[VulnWatch] RAZOR advisory: Linux util-linux chfn local root vulnerability

From: Michal Zalewski (lcamtuf@coredump.cx)
Date: 07/29/02


Date: Mon, 29 Jul 2002 10:51:50 -0400 (EDT)
From: Michal Zalewski <lcamtuf@coredump.cx>
To: bugtraq@securityfocus.com, <vulnwatch@vulnwatch.org>


Linux util-linux chfn local root vulnerability

   Issue Date: July 29, 2002
   Contact: Michal Zalewski
   CVE: CAN-2002-0638
   CERT vulnerability note: http://www.kb.cert.org/vuls/id/405955
   (the URL should be accessible soon)

Topic:

   A locally exploitable vulnerability is present in the util-linux
   package shipped with Red Hat Linux and numerous other Linux
   distributions.

Affected Systems:

   Red Hat Linux 7.3 and previous; potentially many other distributions
   up to date that use util-linux to provide chfn and chsh utilities.
   Please refer to the CERT vulnerability note for more information.

   Systems that ship chfn within the shadow-utils package (for example
   SuSE) are not vulnerable.

Details:

   The vulnerability is present within the shared code of the util-linux
   package. For the purpose of this discussion, we use the setuid
   application 'chfn' as an attack vector. We used the "Fenris" utility,
   available at http://razor.bindview.com/tools/fenris, to analyze the
   issue.

   The purpose of the 'chfn' utility is to allow users to modify personal
   information stored within the system-wide password file, /etc/passwd.
   In order to modify this file, this application must be installed
   setuid root. It is a part of the base installation of most Linux
   systems.

   Under certain commonly met conditions (see "Mitigating Factors"), a
   carefully crafted attack sequence can be performed to exploit a
   complex file locking and modification race present in this utility,
   and, as a result, alter /etc/passwd to escalate privileges in the
   system.

   Password file locking and modification code can be found in util-linux
   within the login-utils/setpwnam.c file. The general logic used for
   this process is as follows:

    1. /etc/ptmptmp file is opened with O_WRONLY|O_CREAT, 0644 perms
    2. the file is linked to /etc/ptmp, exit on failure
    3. /etc/ptmptmp is removed

   Later, the descriptor obtained in step 1 is used for writing to
   construct the new /etc/passwd contents. This is done line by line, by
   calling the fputs() routine. When the new file is ready, three more
   steps are taken:

    4. /etc/passwd.OLD is removed
    5. /etc/passwd is linked to /etc/passwd.OLD
    6. /etc/ptmp is renamed to /etc/passwd

   At first sight, this logic is not flawed. There is no O_EXCL in step
   1, but step 2 works as an accurate exclusive locking mechanism, and
   prevents other instances of this application from making any writes
   until the procedure is completed (step 6). Also, step 3 ensures that
   no process will work on the hardlink of /etc/passwd after the
   procedure is completed. This, while is not a standard way of handling
   file locking on Linux, is an adequate protection against race
   conditions. The only concern is that if chfn, chsh, or any other
   process that uses /etc/ptmp is terminated abnormally (e.g. never exits
   because of a system crash or is killed with SIGKILL), the lock has to
   be removed manually in order for applications that rely on this
   signaling method to work properly. This includes utilities such as
   chsh, chfn, useradd, userdel, adduser, vipw and other software. Most
   of seasoned Linux administrators have had to remove this lock file at
   least once, and many utilities offer help troubleshooting this issue -
   for example, adduser refuses to run when /etc/ptmp is present and
   terminates with the following message:

   # adduser
   vipw lockfile (/etc/ptmp) is present!

   This is exactly what causes the problem. Because this dangling lock
   file makes it impossible to perform regular user management tasks, it
   is trivial to force the administrator to remove it. This is a risk if
   chfn is stopped (by SIGSTOP) between steps 2 and 3 - which can be
   done. There is a very small window of opportunity here, but it is
   possible to fit into it. Later, the application can be resumed with
   SIGCONT.

   Note that there is no easy way to diagnose who created /etc/ptmp and
   for what reason they created it. The file is owned by root and has no
   additional information whatsoever. The attack relies on small windows
   of opportunity, making one-shot attempts not feasible on certain
   systems, it is reasonable to expect that if the attacker repeatedly
   runs chfn or chsh just to create dangling lock files several times a
   day - and to make user management and maintenance tasks repeatedly
   fail because of this lockfile - the administrator will most likely add
   "rm -f /etc/ptmp" or equivalent to his crontab, instead of putting
   much more effort into tracing the problem that cannot be easily
   diagnosed using standard Linux tools - there is no log entry
   associated with successful authentication for chfn service, ownership
   of the file is set to root, the file is empty.

   As soon as this or a similar counter-measure is deployed,
   trial-and-error attacks become much simpler. None of the above
   administrative tasks is to be viewed as requiring social engineering.
   Such tasks are a part of standard system maintenance and operations,
   and it is safe to assume that almost all system administrators will
   follow this or a similar procedure to address the problem.

   Once the attacker has a stopped chfn process in between steps 2 and 3,
   and the /etc/ptmp file has been removed, he can easily execute another
   instance of chfn. It will open the already existing /etc/ptmptmp (this
   file is not considered a lock by any software, and most likely won't
   be deleted). The second instance runs and step 2 succeeds because
   there is no lock file present. The file is then written. As soon as
   /etc/ptmp has the projected size (or /etc/passwd.OLD appears), the
   instance should be killed to avoid renaming to /etc/passwd (this is a
   timing concern for this attack, and we will address it later).

   The First instance of chfn is still holding an open descriptor to
   /etc/ptmptmp, which later became /etc/ptmp - and, if we send SIGCONT
   to this process, will be renamed to /etc/passwd. Step 3 will fall
   through because there is no error checking, and new information will
   be written into a descriptor that will de facto become /etc/passwd.

   Copying the file should go smoothly up to the point where attacker
   account information is stored. Let's analyze what is going to happen
   past this point if the second instance of chfn was called with
   significantly longer GECOS parameters than first (minimum GECOS length
   set by chfn is zero, maximum is 260 characters). Keep in mind that
   there is no call to ftruncate() before fclose(), so the new file would
   have a "tail" consisting of the last characters of the longer version
   of /etc/passwd. If 'Write 1' is the first write to file, or the write
   made by the second instance of chfn, 'Write 1 is the second write
   (first instance), and '|' represents a record separator (newline),
   then:

   Write 1: user 1 | user 2 | ATTACKER LONG | user 3 | user 4 |
   Write 2: user 1 | user 2 | ATTACKER | user 3 | user 4 |
   ------------------------------------------------------------
   Result: user 1 | user 2 | ATTACKER | user 3 | user 4 |r 4 |

   As a result, /etc/passwd has now a new line at the end, a "leftover"
   part from last username. This, itself, can be considered an annoyance,
   but is hardly a full-blown security issue. There is, however,
   something that makes this problem more serious - that is, GNU libc.

   As we mentioned earlier, records are being written to wannabe
   /etc/passwd line by line, using fputs(). It is important to realize
   that the text-file functions such as fputs() are buffered - that is,
   lines are being actually written to disk in chunks, when internal
   cache is full. Until this point, the OS's idea of the file and the
   process's idea of the file are not synchronized. Cache block size, by
   default, is four kilobytes. This means that if the password file has
   more than four kilobytes (which is very often true on systems where
   local attacks are performed, see "Mitigating Factors"), and the
   attacker's account is not in the last cached that is going to be
   written before fclose(), it should be possible to "cut" the file on
   four kilobytes boundary by killing the first chfn process as soon as
   /etc/passwd has the desired contents:

                   <--- attacker's account 4 kB boundary
                                                       |
   Write 1: ...ser1:x:640:640:Foo Bar:/bin/bash\nuser2:x:641:641:Foo...
   Write 2: ...user1:x:640:640:Foo Bar:/bin/bash\nuser2: ***CUT***
   --------------------------------------------------------------------
   Result: ...\nuser2::641:641:Foo...

   Note that both the data written in "Write 1" and in "Write 2" can be
   padded within a 260 byte range, which is typically much more than a
   typical /etc/passwd line length, so it is possible to pad the record
   exactly in the way described above - which happens to erase the
   password for user2. All it takes is to determine the appropriate
   initial padding, then invoke the first chfn with one more character in
   GECOS than in the second chfn.

   It is also possible to create root accounts or other privileged access
   accounts that effectively lead to privilege escalation (kmem, mail,
   etc). A root account with no password can be generated in two steps,
   first truncate the UID ending with '0' to the last digit, and the
   second step is to remove the password as described above. Note that
   even if there is no account with UID 0 anywhere close to the 4 kB
   boundary, data can be moved over much longer distance by several
   subsequent attack cycles where the first instance is called with the
   maximum length GECOS field, and the second with minimum.

   The question of timing required for trimming is essential in
   evaluating the feasibility of this attack - but, fortunately for the
   attacker, timing is on his side. First of all, the copying procedure
   is essentially pretty slow, being done in a read-compare-write cycle.
   Then, the results are cached so the output appears in larger chunks,
   but less frequently, giving the attacker plenty of CPU cycles.
   Finally, the passwd file is world-readable, which gives the attacker a
   tremendous advantage - it can be easily monitored e.g. by mapping a
   single byte at the boundary to the memory space of the exploit.
   Additionally, for files that consist of more than one segment past the
   attacker's account, the problem of careful timing needed to avoid
   renaming /etc/ptmp to /etc/passwd becomes less of an issue, because
   the process can be killed before reaching the end of the file, just
   after the desired boundary is modified.

Mitigating factors:

   In order to successfully exploit the vulnerability and perform
   privilege escalation, the following additional requirements have to be
   met:

    1. There is a need for a minimal administrator interaction that falls
       into the category of standard system maintenance and operations.

    2. The password file, /etc/passwd, must be over 4 kilobytes. This
       requirement is typically satisfied on systems with approximately
       50 accounts or more - that is, on a vast majority of systems that
       where unprivileged accounts are being compromised / used by
       malicious attackers.

    3. Assume that /etc/passwd is divided into 4 kB chunks. The
       attacker's account record in this file must NOT be located in the
       last chunk. That is, if /etc/passwd has 9 kB and we have three
       chunks, 4 kB, 4 kB and 1 kB, and attacker's account must be
       located within the first or second chunk and cannot be located
       further than 8th kilobyte of the file.

   Other mitigating factors are similar to ones for other local
   vulnerabilities in setuid binaries.

Workaround / fix:

   The fast workaround is to remove setuid flags from /usr/bin/chfn and
   /usr/bin/chsh. A more appropriate fix is to patch the sources as
   follows:

--- util-linux-2.11n-old/login-utils/setpwnam.c Mon Jul 31 08:50:39 2000
+++ util-linux-2.11n/login-utils/setpwnam.c Wed Jun 12 21:37:12 2002
@@ -98,7 +98,8 @@
     /* sanity check */
     for (x = 0; x < 3; x++) {
        if (x > 0) sleep(1);
- fd = open(PTMPTMP_FILE, O_WRONLY|O_CREAT, 0644);
+ // Never share the temporary file.
+ fd = open(PTMPTMP_FILE, O_WRONLY|O_CREAT|O_EXCL, 0644);
        if (fd == -1) {
            umask(oldumask);
            return -1;

Vendor Response:

   Please refer to CERT VU#405955 for more information.

Additional credits:

   Thanks to Chris Coffin and Mark Loveless for their contributions
   to this advisory.



Relevant Pages