From nobody  Fri May 29 11:44:37 1998
Received: (from nobody@localhost)
          by hub.freebsd.org (8.8.8/8.8.8) id LAA07680;
          Fri, 29 May 1998 11:44:37 -0700 (PDT)
          (envelope-from nobody)
Message-Id: <199805291844.LAA07680@hub.freebsd.org>
Date: Fri, 29 May 1998 11:44:37 -0700 (PDT)
From: cts@internetcds.com
To: freebsd-gnats-submit@freebsd.org
Subject: race condition in pw command 
X-Send-Pr-Version: www-1.0

>Number:         6787
>Category:       bin
>Synopsis:       race condition in pw command
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    nate
>State:          closed
>Quarter:        
>Keywords:       
>Date-Required:  
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Fri May 29 11:50:03 PDT 1998
>Closed-Date:    Thu Jul 16 10:20:49 PDT 1998
>Last-Modified:  Thu Jul 16 10:22:13 PDT 1998
>Originator:     Craig Spannring
>Release:        2.2.6
>Organization:
InternetCDS
>Environment:
FreeBSD bangkok.office.cdsnet.net 2.2.6-STABLE FreeBSD 2.2.6-STABLE #1: Tue Apr 28 16:38:57 PDT 1998     root@bangkok.office.cdsnet.net:/usr/src/sys/compile/BANGKOK  i386

>Description:
vipw, chpass and passwd all lock the /etc/master.passwd file for the
entire time they run.  pw tries not to hold the lock the entire time.
Specifically it does not have the file locked when it calls pwd_mkdb.
This causes some race conditions since multiple instances of pwd_mkdb
can run at the same time.

I think pw should take a pessimistic approach and keep the
/etc/master.passwd file locked the entire time just like passwd, vipw
and chpass.  My quick fix for pw opens /etc/master.passwd in main()
with the O_EXLOCK flag in pw.c and doesn't close the file descriptor.
I also had to make sure that fileupdate() in fileupd.c does not try to
lock the /etc/master.passwd file.

I tried to contact David L. Nugent, <davidn@blaze.net.au> last week
but I have heard back from him yet.

I believe this problem exists in the -current tree as well as -stable.
>How-To-Repeat:
The following perl5 script will demonstrate the race condition.  It
forks 10 copies of itself and each one tries to create and delete 100
users.  With a fixed version of pw there will be no error messages
from pwd_mkdb and it will delete all of the new accounts before it finishes.  

With the current version of pw you wind up with a large number of
accounts that aren't deleted because of the race condition and a large
number of messages from pwd_mkdb such as-

  pwd_mkdb: /etc/pwd.db.tmp: File exists
  pwd_mkdb: /etc/pwd.db.tmp to /etc/pwd.db: No such file or directory
  pwd_mkdb: /etc/pwd.db.tmp: No such file or directory
  pwd_mkdb: /etc/spwd.db.tmp: File exists



#!/usr/local/bin/perl -w
#
#
# Demonstrate pw glitch that occurs when multiple instances of pw run
# at the same time.
#

use strict 'vars';
use strict 'subs';

use diagnostics;
use diagnostics -verbose;
enable diagnostics;

my $a;
my $b;
my $i;
my $notAdded      = 0;
my $notDeleted    = 0;
my $wasAdded      = 0;
my $pwAddFailures = 0;
my $pwDelFailures = 0;

my $PW="/usr/sbin/pw";

for($i=0; $i<10; $i++)
{
    my $rc = fork();
    if ($rc == -1)
    {
        print STDERR "Couldn't fork\n";
        exit(1);
    }
    elsif ($rc == 0)
    {
        next;
    }
    else
    {
        foreach $a ('0'..'9')
        {
            foreach $b ('0'..'9')
            {
                my $username = "u$a$b$$";
                my $cmd = "$PW useradd $username -d /tmp/$a/$b/$username";
                
                mkdir("/tmp/$a", 0755);
                mkdir("/tmp/$a/$b", 0755);

                print "system($cmd);\n";
                system($cmd)==0 || $pwAddFailures++;
                if (!defined(getpwnam($username)))
                {
                    $notAdded++;
                    $wasAdded=1;
                }
                else
                {
                    $wasAdded=1;
                }
                $cmd = "$PW userdel $username";
                print "system($cmd);\n";
                system($cmd)==0 || $pwDelFailures++;
                if ($wasAdded && defined(getpwnam($username)))
                {
                    $notDeleted++;
                }
            }
        }
        open (STATUS, ">/tmp/status.$$");
        print STATUS "'pw adduser' recorded $pwAddFailures failures.\n";
        print STATUS "'pw deluser' recorded $pwDelFailures failures.\n";
        print STATUS "$notAdded accounts weren't added, $notDeleted accounts weren't deleted\n";
        print STATUS "$notAdded accounts weren't added, $notDeleted accounts weren't deleted\n";
        
    }
}


>Fix:
main() in pw.c should be modified to set an exclusive lock 
on /etc/master.passwd.

The function fileupdate in fileupd.c should be modified so that it does not 
lock /etc/master.passwd.

>Release-Note:
>Audit-Trail:

From: Nate Williams <nate@mt.sri.com>
To: cts@internetcds.com
Cc: freebsd-gnats-submit@FreeBSD.ORG
Subject: Re: bin/6787: race condition in pw command 
Date: Fri, 29 May 1998 20:20:50 -0600

 > >Number:         6787
 > >Category:       bin
 > >Synopsis:       race condition in pw command
 > >Fix:
 > main() in pw.c should be modified to set an exclusive lock 
 > on /etc/master.passwd.
 > 
 > The function fileupdate in fileupd.c should be modified so that it does not 
 > lock /etc/master.passwd.
 
 Context diffs to sources will be appreciated. :)
 
 
 Nate
 
 ps. Hi Craig!

From: Craig Spannring <cts@internetcds.com>
To: Nate Williams <nate@mt.sri.com>
Cc: freebsd-gnats-submit@FreeBSD.ORG
Subject: Re: bin/6787: race condition in pw command 
Date: Mon, 1 Jun 1998 11:18:09 -0700 (PDT)

 At CDS we only use pw for adding and deleting accounts.  My fix
 doesn't really address adding/deleting groups and therefore isn't
 appropriate for the general case.
 
 If you would like I'll go through and make the appropriate changes for
 the general case.
 
 PS.  How's life in Helena?
 
 -- 
 =======================================================================
  Life is short.                  | Craig Spannring 
       Ski hard, Bike fast.       | cts@internetcds.com
  --------------------------------+------------------------------------
  Any sufficiently perverted technology is indistinguishable from Perl.
 =======================================================================
State-Changed-From-To: open->suspended 
State-Changed-By: phk 
State-Changed-When: Tue Jun 2 02:59:22 PDT 1998 
State-Changed-Why:  
awaiting patches & committer 

From: Poul-Henning Kamp <phk@critter.freebsd.dk>
To: freebsd-gnats-submit@freebsd.org
Cc:  Subject: Re: bin/6787
Date: Thu, 02 Jul 1998 07:47:22 +0200

 ------- Forwarded Message
 
 Return-Path: paul@mu.org
 Received: from critter.freebsd.dk (localhost [127.0.0.1])
 	by critter.freebsd.dk (8.8.7/8.8.5) with ESMTP id HAA02061
 	for <phk@localhost>; Thu, 2 Jul 1998 07:46:04 +0200 (CEST)
 Received: from phk.freebsd.dk
 	by critter.freebsd.dk (fetchmail-4.3.2 POP3 run by phk)
 	for <phk@localhost> (single-drop); Thu Jul  2 07:46:05 1998
 Received: from cicero1.cybercity.dk (cicero1.cybercity.dk [195.8.135.242])
 	by phk.freebsd.dk (8.8.8/8.8.5) with ESMTP id HAA28388
 	for <phk@phk.freebsd.dk>; Thu, 2 Jul 1998 07:44:52 +0200 (CEST)
 Received: from hub.freebsd.org (hub.FreeBSD.ORG [204.216.27.18])
 	by cicero1.cybercity.dk (8.8.7/8.8.7) with ESMTP id HAA03076
 	for <phk@phk.freebsd.dk>; Thu, 2 Jul 1998 07:44:36 +0200 (CEST)
 	(envelope-from paul@priscilla.mu.org)
 Received: from priscilla.mu.org (paul@priscilla.mu.org [206.156.231.1])
           by hub.freebsd.org (8.8.8/8.8.8) with ESMTP id WAA06958
           for <phk@freebsd.org>; Wed, 1 Jul 1998 22:44:31 -0700 (PDT)
           (envelope-from paul@priscilla.mu.org)
 Received: (from paul@localhost)
 	by priscilla.mu.org (8.8.8/8.8.8) id AAA26073;
 	Thu, 2 Jul 1998 00:43:28 -0500 (CDT)
 	(envelope-from paul)
 Message-ID: <19980702004328.A26035@mu.org>
 Date: Thu, 2 Jul 1998 00:43:28 -0500
 From: Paul Saab <paul@mu.org>
 To: phk@freebsd.org
 Subject:  (fwd)
 Mime-Version: 1.0
 Content-Type: text/plain; charset=us-ascii
 X-Mailer: Mutt 0.91.1i
 X-UIDL: 30b644c646ae528665ec85a66edefd93
 
 Poul,
 
 I dont know if this is the right solution to pr 6787 but no-one
 else seems to want to fix it so here is something.
 
 Paul
 
 Index: Makefile
 ===================================================================
 RCS file: /usr/cvs/src/usr.sbin/pw/Makefile,v
 retrieving revision 1.5
 diff -u -r1.5 Makefile
 - --- Makefile	1997/02/22 16:12:17	1.5
 +++ Makefile	1998/07/02 05:39:45
 @@ -3,13 +3,16 @@
  PROG=	pw
  SRCS=	pw.c pw_conf.c pw_user.c pw_group.c pw_log.c pw_nis.c \
  	grupd.c pwupd.c fileupd.c edgroup.c psdate.c \
 - -	bitmap.c cpdir.c rm_r.c
 +	bitmap.c cpdir.c rm_r.c pw_util.c
  
 +.PATH: ${.CURDIR}/../../usr.sbin/vipw
 +
  MAN5=	pw.conf.5
  MAN8=	pw.8
  
  #RND=	-DUSE_MD5RAND
  CFLAGS+= -Wall $(CDB) $(RND)
 +CFLAGS+= -I${.CURDIR}/../../usr.sbin/vipw
  LDADD=	-lcrypt
  DPADD=	${LIBCRYPT}
  
 Index: pwupd.c
 ===================================================================
 RCS file: /usr/cvs/src/usr.sbin/pw/pwupd.c,v
 retrieving revision 1.7
 diff -u -r1.7 pwupd.c
 - --- pwupd.c	1998/02/11 23:31:24	1.7
 +++ pwupd.c	1998/07/02 05:39:45
 @@ -41,6 +41,8 @@
  
  #include "pwupd.h"
  
 +char *tempname;
 +
  #define HAVE_PWDB_C	1
  
  static int
 @@ -137,8 +139,11 @@
  			 */
  			if (pwd != NULL)
  				fmtpwentry(pwbuf, pwd, PWF_MASTER);
 - -			if ((rc = fileupdate(_PATH_MASTERPASSWD, 0644, pwbuf, pfx, l, mode)) != 0)
 +			if ((rc = fileupdate(_PATH_MASTERPASSWD, 0644, pwbuf, pfx, l, mode)) != 0) {
 +				int	fd = pw_lock();
  				rc = pwdb(NULL) == 0;
 +				close(fd);
 +			}
  		}
  	}
  	return rc;
 
 
 ------- End of Forwarded Message

Subject Re: bin/6787: race condition in pw command
In-Reply-To: <199806020959.CAA15603@freefall.freebsd.org>
References: <199806020959.CAA15603@freefall.freebsd.org>
X-Mailer: VM 6.31 under Emacs 20.2.1



Poul-Henning Kamp writes:
 > Synopsis: race condition in pw command
 > 
 > State-Changed-From-To: open->suspended
 > State-Changed-By: phk
 > State-Changed-When: Tue Jun 2 02:59:22 PDT 1998
 > State-Changed-Why: 
 > awaiting patches & committer

Poul, 

Was there a problem with the patches I sent to you and Nate Williams
on June 8, 1998?

BTW-  I don't think the patch submitted by Paul Saab <paul@mu.org> will
work.  When the function fileupd in fileupd.c tries to get an
exclusive lock on the file it will block forever.  You can easily test
it with the Perl program given in the "How-To-Repeat" section of the
PR.

In case you don't have it here is my patch for 2.2.6 and description
from June 2.
-----------------------------------------

These are the patches to pw for -stable.  I will send the patches for
-current in the next mail message.

I tried to follow the same style as charnier(?).  One spot where I
disagree with the style is near the end of the main function in pw.c.
He reuses the 'ch' variable to hold the return status of the

  funcs[which] (cnf, mode, &arglist)

call.  It overloads the meaning of 'ch' and is not very descriptive,
but I left it as is.

The fileupdate function is still somewhat broken.  Instead of returning
a failure code if it can't modify the original file it renames the
.new file and continues as though nothing is wrong.  This will cause
the lock on the original file to be lost and could lead to a similar
race condition.  I left that portion of the code alone since I feel
that the maintainer of the code would have a better concept of how he
wants to handle errors in that function than I do.

BTW-  I used 'diff -c -r pw-stable.original pw-stable' to generate
this patch.


State-Changed-From-To: suspended->closed 
State-Changed-By: nate 
State-Changed-When: Thu Jul 16 10:20:49 PDT 1998 
State-Changed-Why:  
Submitters fix applied to both -stable and -current. 

(Sorry for the *really* liong delay in getting to this Craig!) 


Responsible-Changed-From-To: freebsd-bugs->nate 
Responsible-Changed-By: nate 
Responsible-Changed-When: Thu Jul 16 10:20:49 PDT 1998 
Responsible-Changed-Why:  
I committed the diffs. :) 
>Unformatted:
