Re: Locking the passwd subsystem (was Re: bug in pw, -STABLE [patch])

2002-06-23 Thread Matthew D. Fuller

And here's a run at Stage 2.  This adapts a subset of programs to use the
pid_*() locking supplied.  The attached patch updates: , libutil
(libutil.h and pw_util.c), chpass (chpass.c), pw (pw.c), pwd_mkdb
(Makefile, pwd_mkdb.8, pwd_mkdb.c), vipw (vipw.c).  This does NOT include
passwd(1), since that does its dirty work in PAM, which I definately
don't feel like delving into.

Note that this patch MAY be a little off when applied over the previous
patch; I'm working on them in seperate trees, to try and keep the stages
discrete.  But a little manual fiddling should get them reconciled in
short order.


-- 
Matthew Fuller (MF4839)   |  [EMAIL PROTECTED]
Systems/Network Administrator |  http://www.over-yonder.net/~fullermd/

"The only reason I'm burning my candle at both ends, is because I
  haven't figured out how to light the middle yet"


Index: include/pwd.h
===
RCS file: /usr/cvs/src/include/pwd.h,v
retrieving revision 1.12
diff -u -r1.12 pwd.h
--- include/pwd.h   9 Jun 2002 19:39:18 -   1.12
+++ include/pwd.h   23 Jun 2002 18:16:21 -
@@ -66,6 +66,9 @@
 #define_PATH_MASTERPASSWD  "/etc/master.passwd"
 #define_MASTERPASSWD   "master.passwd"
 
+#define_PATH_PWDLOCK   "/var/run/pwd.lock"
+#define_MODE_PWDLOCK   (S_IRUSR | S_IWUSR)
+
 #define_PATH_MP_DB "/etc/pwd.db"
 #define_MP_DB  "pwd.db"
 #define_PATH_SMP_DB"/etc/spwd.db"
Index: lib/libutil/libutil.h
===
RCS file: /usr/cvs/src/lib/libutil/libutil.h,v
retrieving revision 1.37
diff -u -r1.37 libutil.h
--- lib/libutil/libutil.h   8 May 2002 00:50:07 -   1.37
+++ lib/libutil/libutil.h   23 Jun 2002 18:38:18 -
@@ -95,6 +95,8 @@
 intpw_init(const char *_dir, const char *_master);
 char   *pw_make(struct passwd *_pw);
 intpw_mkdb(const char *_user);
+intpw_globlock(int);
+intpw_globunlock(void);
 intpw_lock(void);
 struct passwd *pw_scan(const char *_line, int _flags);
 const char *pw_tempname(void);
@@ -124,6 +126,9 @@
 #defineFPARSELN_UNESCCOMM  0x04
 #defineFPARSELN_UNESCREST  0x08
 #defineFPARSELN_UNESCALL   0x0f
+
+/* pw_globlock() */
+#definePW_NOBLOCK  PID_NOBLOCK
 
 /* pw_scan() */
 #define PWSCAN_MASTER  0x01
Index: lib/libutil/pw_util.c
===
RCS file: /usr/cvs/src/lib/libutil/pw_util.c,v
retrieving revision 1.25
diff -u -r1.25 pw_util.c
--- lib/libutil/pw_util.c   8 May 2002 14:52:32 -   1.25
+++ lib/libutil/pw_util.c   23 Jun 2002 18:46:19 -
@@ -79,6 +79,7 @@
 static char passwd_dir[PATH_MAX];
 static char tempname[PATH_MAX];
 static int initialized;
+static int globlocked = 0;
 
 void
 pw_cont(int sig)
@@ -163,6 +164,28 @@
 }
 
 /*
+ * Lock the password subsystem globally.
+ */
+int
+pw_globlock(int flags)
+{
+   int retval;
+
+   if( (retval=pid_begin(_PATH_PWDLOCK, _MODE_PWDLOCK, flags)) == 0 );
+   globlocked=1;
+   return(retval);
+}
+
+/*
+ * Unlock the global lock
+ */
+int pw_globunlock(void)
+{
+   
+   return(pid_end(_PATH_PWDLOCK));
+}
+
+/*
  * Lock the master password file.
  */
 int
@@ -258,10 +281,10 @@
case 0:
/* child */
if (user == NULL)
-   execl(_PATH_PWD_MKDB, "pwd_mkdb", "-p",
+   execl(_PATH_PWD_MKDB, "pwd_mkdb", "-p", "-n",
"-d", passwd_dir, tempname, NULL);
else
-   execl(_PATH_PWD_MKDB, "pwd_mkdb", "-p",
+   execl(_PATH_PWD_MKDB, "pwd_mkdb", "-p", "-n",
"-d", passwd_dir, "-u", user, tempname, NULL);
_exit(1);
default:
@@ -353,6 +376,11 @@
}
if (lockfd != -1)
close(lockfd);
+   if (globlocked != 0)
+   {
+   globlocked=0;
+   pid_end(_PATH_PWDLOCK);
+   }
errno = serrno;
 }
 
Index: usr.bin/chpass/chpass.c
===
RCS file: /usr/cvs/src/usr.bin/chpass/chpass.c,v
retrieving revision 1.23
diff -u -r1.23 chpass.c
--- usr.bin/chpass/chpass.c 8 May 2002 00:54:28 -   1.23
+++ usr.bin/chpass/chpass.c 23 Jun 2002 18:42:25 -
@@ -258,6 +258,8 @@
case _PWF_FILES:
if (pw_init(NULL, NULL))
err(1, "pw_init()");
+   if (pw_globlock(PW_NOBLOCK) < 0)
+   err(1, "pw_globlock()");
if ((pfd = pw_lock()) == -1) {
pw_fini();
err(1, "pw_lock()");
Index: usr.sbin/pw/pw.c
===
RCS file: /usr/

Re: Locking the passwd subsystem (was Re: bug in pw, -STABLE [patch])

2002-06-23 Thread Matthew D. Fuller

On Sun, Jun 23, 2002 at 11:17:35AM -0400 I heard the voice of
Geoffrey C. Speicher, and lo! it spake thus:
>
> 5-minute window.  Still unliklely, but not quite as unlikely as the
> non-daemon scenario.

And it all goes out when window when kern.randompid=1, so it's kinda
moot.


> Looks good from here.  I've attached some minor patches (also relative to
> src/) to pid_util.{3,c}.  The first corrects some minor typos in the man
> page and the second is to make pid_begin() work as advertised when read()
> fails (set errno and return -1 rather than return errno).

Yes, I have a brain.  No, really I do.  I just don't have enough coffee
:)


> (I haven't tested any of this yet either.)

I just slapped together a few quick test programs.  Here's an updated
patch, incorporating your fixes, as well as a bugfix and an almost-bugfix
my testing showed up (hint: just because you ftruncate() to 0-length,
doesn't mean your offset becomes 0 too, dangit), and eliminated an extra
variable.


-- 
Matthew Fuller (MF4839)   |  [EMAIL PROTECTED]
Systems/Network Administrator |  http://www.over-yonder.net/~fullermd/

"The only reason I'm burning my candle at both ends, is because I
  haven't figured out how to light the middle yet"


Index: lib/libutil/Makefile
===
RCS file: /usr/cvs/src/lib/libutil/Makefile,v
retrieving revision 1.46
diff -u -r1.46 Makefile
--- lib/libutil/Makefile8 May 2002 00:50:07 -   1.46
+++ lib/libutil/Makefile23 Jun 2002 13:31:01 -
@@ -8,7 +8,7 @@
 CFLAGS+=-DINET6
 SRCS=  _secure_path.c auth.c fparseln.c login.c login_auth.c \
login_cap.c login_class.c login_crypt.c login_ok.c login_times.c \
-   login_tty.c logout.c logwtmp.c property.c pty.c \
+   login_tty.c logout.c logwtmp.c pid_util.c property.c pty.c \
pw_util.c realhostname.c stub.c \
trimdomain.c uucplock.c
 INCS=  libutil.h login_cap.h
@@ -16,7 +16,7 @@
 MAN+=  login.3 login_auth.3 login_tty.3 logout.3 logwtmp.3 pty.3 \
login_cap.3 login_class.3 login_times.3 login_ok.3 \
_secure_path.3 uucplock.3 property.3 auth.3 realhostname.3 \
-   realhostname_sa.3 trimdomain.3 fparseln.3
+   realhostname_sa.3 trimdomain.3 fparseln.3 pid_util.3
 MAN+=  login.conf.5 auth.conf.5
 MLINKS+= property.3 properties_read.3  property.3 properties_free.3
 MLINKS+= property.3 property_find.3
@@ -39,5 +39,6 @@
 MLINKS+=login_auth.3 auth_checknologin.3 login_auth.3 auth_cat.3
 MLINKS+=uucplock.3 uu_lock.3 uucplock.3 uu_lock_txfr.3 \
uucplock.3 uu_unlock.3 uucplock.3 uu_lockerr.3
+MLINKS+=pid_util.3 pid_begin.3 pid_end.3
 
 .include 
Index: lib/libutil/libutil.h
===
RCS file: /usr/cvs/src/lib/libutil/libutil.h,v
retrieving revision 1.37
diff -u -r1.37 libutil.h
--- lib/libutil/libutil.h   8 May 2002 00:50:07 -   1.37
+++ lib/libutil/libutil.h   23 Jun 2002 12:35:30 -
@@ -82,6 +82,8 @@
 struct sockaddr;
 intrealhostname_sa(char *host, size_t hsize, struct sockaddr *addr,
 int addrlen);
+int pid_begin(const char *pidfile, mode_t mode, int flags);
+int pid_end(const char *pidfile);
 #ifdef _STDIO_H_   /* avoid adding new includes */
 char   *fparseln(FILE *, size_t *, size_t *, const char[3], int);
 #endif
@@ -128,5 +130,8 @@
 /* pw_scan() */
 #define PWSCAN_MASTER  0x01
 #define PWSCAN_WARN0x02
+
+/* pid_begin() */
+#define PID_NOBLOCK0x01
 
 #endif /* !_LIBUTIL_H_ */
Index: lib/libutil/pid_util.c
===
--- /dev/null   Sun Jun 23 11:44:00 2002
+++ lib/libutil/pid_util.c  Sun Jun 23 11:50:59 2002
@@ -0,0 +1,178 @@
+/*-
+ * Copyright (c) 2002 Matthew D. Fuller <[EMAIL PROTECTED]>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *notice, this list of conditions and the following disclaimer in the
+ *documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABI

Re: Locking the passwd subsystem (was Re: bug in pw, -STABLE [patch])

2002-06-23 Thread Geoffrey C. Speicher

On Sun, 23 Jun 2002, Matthew D. Fuller wrote:

> Well, a daemon would have a BETTER chance, though nowhere near a
> guarantee.  The theory being that the daemon would check every so often
> (5 minutes sounds good to me), whereas with the library, you potentially
> could not check for days, depending on how often you run pw(8).  Even on
> a heavily used system, 5 minutes wouldn't likely be enough to reuse a
> PID, unless you had random PID assignment or some such.

Right, for programs like pw(8), the daemon should definitely suffice for
all practical purposes.  For daemons, it's another story.  You don't
necessarily have to wrap around the pid list inside of those 5 minutes.
If the original program runs for a long time (which is likely for
daemons), then the pid list could wrap around while it is still running.
Then it is possible that it dies and its pid gets reuser within that
5-minute window.  Still unliklely, but not quite as unlikely as the
non-daemon scenario.

> > > Now, this is a problem.  There's a race condition here.  It's a very
> > > small window, to be sure, but I'm not quite sure how to close it.  After
> >
> > I think you must've figured it out judging by your post that just came
> > through as I was writing that previous paragraph.  :)
>
> I think so.  See comments in the code.

I think so too.  I guess we need those flock-style locks after all.

> Code is reasonably well commented.  Manpage included.  Shaken, not
> stirred.  Slippery when wet.  This has gone through a compile test, but
> hasn't been live-tested.

Looks good from here.  I've attached some minor patches (also relative to
src/) to pid_util.{3,c}.  The first corrects some minor typos in the man
page and the second is to make pid_begin() work as advertised when read()
fails (set errno and return -1 rather than return errno).

(I haven't tested any of this yet either.)

You da man.

Geoff


--- lib/libutil/pid_util.3.orig Sun Jun 23 10:31:39 2002
+++ lib/libutil/pid_util.3  Sun Jun 23 10:35:22 2002
@@ -53,7 +53,7 @@
 still exists.
 If the process is still alive,
 .Fn pid_begin
-will block and continue to try aquiring the lock indefinately, unless
+will block and continue to try aquiring the lock indefinitely, unless
 .Dv PID_NOBLOCK
 is set in the
 .Fa flags ,
@@ -63,7 +63,7 @@
 will put its own process ID in the file and continue on its merry way.
 .Pp
 .Fn pid_end
-removed the file referenced by
+removes the file referenced by
 .Fa pidfile .
 Note that there is currently no protection afforded here that the process
 calling
@@ -86,9 +86,9 @@
 The operation was cancelled due to internal error.
 .It Bq Er EWOULDBLOCK
 The file is already locked by a still-live process and
-.Fa mode
+.Fa flags
 includes
-.Dv PID_NOLOCK .
+.Dv PID_NOBLOCK .
 .El
 .Pp
 The


--- lib/libutil/pid_util.c.orig Sun Jun 23 10:36:34 2002
+++ lib/libutil/pid_util.c  Sun Jun 23 10:54:58 2002
@@ -92,7 +92,8 @@
{
holding==errno;
close(lockfd);
-   return(holding);
+   errno=holding;
+   return(-1);
}
 
masterpid = (pid_t) atoi(readpid);



Locking the passwd subsystem (was Re: bug in pw, -STABLE [patch])

2002-06-23 Thread Matthew D. Fuller

[ Moved to -hackers since this isn't really a STABLE-specific issue, but
rather a general thing.  Bcc's to -stable for continuity's sake. ]

This discussion has gone on, on and off, for months now, so I'm going to
recap a bit.  For the people in it, we've mostly paged out the details by
now, and for the people not in it, this will be necessary catchup.

pw(8) can sometimes corrupt the passwd file when it's running.  This is
due to the fact that, while it munges things, it locks
/etc/master.passwd.  This fails to really work right between invocations,
because as part of its dirty work, pw(8) moves master.passwd away and
creates a new one from scratch.  So, an external lockfile is needed.
There's no real common locking among the other consumers (chpass(1),
passwd(1), vipw(8), pwd_mkdb(8), etc) either, so we might as well make a
big common locking mechanism out of it.

And while we're doing that, it would be nifty to centralize some
functions for doing locking like that, too, instead of coding it from
scratch in every application that needs it.  The patch on this mail
accomplishes this part of the objective; once we can all settle on that,
we can easily handle locking all the consumers neatly using these
functions.


And now, for the carrying on.

On Sun, Jun 23, 2002 at 09:07:15AM -0400 I heard the voice of
Geoffrey C. Speicher, and lo! it spake thus:
> 
> Actually, now that I think about it, even a daemon wouldn't guarantee
> better results in every such scenario.  At first it would seem that the
> daemon would get a chance to clean up the pid file long before another
> process wanted it (during step 4).  However, if the timing is just right
> then p1 can die the moment that its pid would be reused, and chances are
> that the daemon wouldn't catch it in time.

Well, a daemon would have a BETTER chance, though nowhere near a
guarantee.  The theory being that the daemon would check every so often
(5 minutes sounds good to me), whereas with the library, you potentially
could not check for days, depending on how often you run pw(8).  Even on
a heavily used system, 5 minutes wouldn't likely be enough to reuse a
PID, unless you had random PID assignment or some such.


> > Now, this is a problem.  There's a race condition here.  It's a very
> > small window, to be sure, but I'm not quite sure how to close it.  After
> 
> I think you must've figured it out judging by your post that just came
> through as I was writing that previous paragraph.  :)

I think so.  See comments in the code.


(Re: Oliver's statements about the general evil of PID files)
> The beauty of encapsulating the pid file operations into a library is
> that
> the implementation can be changed to create a socket instead of, or (more
> likely) in addition to, a pid file.  In fact, this may be just the thing
> we need to close that "pid gets reused" hole. 
> 
> Since Matt already has the functions mostly implemented, I'll defer to
> him.

Well, there's nothing necessarily stopping us from adding more stuff
later, of course.  Personally, I've set my sights a bit lower; for the
moment, I'm more concerned with "Let's not corrupt the passwd database"
than with "This must never block unless totally absolutely completely
necessary and should always know where its cheese is", which is what the
socket-theory is pointing at.  One giant code upheaval at a time   :)

One possibility would be to make procfs(5) show the process start time
(rather than the current time) for the ctime of the /proc/pid directory.
Then, one could look at the PID file, and if its mtime (not ctime; see in
below code where we could use a pre-existing file) is older than the
ctime of the /proc/pid directory, we'd know the PID had changed.  Getting
the actual process start time would require libkvm (and thus setgid
kmem), or spawning ps(1) and parsing output, otherwise.


ANYWAY:
Attached is a patch (relative to src/) for my first run through creating
the functions to handle PID-file creation, deletion, and
checking-on-creation.  It consists of two functions:
- pid_begin(), which creates a PID file if one doesn't exist, and checks
  to see if the listed process is still around (taking over the PID file
  if it's not) if it does.
- pid_end(), which deletes the PID file.

Code is reasonably well commented.  Manpage included.  Shaken, not
stirred.  Slippery when wet.  This has gone through a compile test, but
hasn't been live-tested.

This will provide the basis then to move forward and lock all the
programs that access the passwd database (for writing, of course; no
reason to lock readers) in a common way.


-- 
Matthew Fuller (MF4839)   |  [EMAIL PROTECTED]
Systems/Network Administrator |  http://www.over-yonder.net/~fullermd/

"The only reason I'm burning my candle at both ends, is because I
  haven't figured out how to light the middle yet"


Index: lib/libutil/Makefile
===
RCS file: /usr/cvs/src/lib/libut