Linux 2.0.x NFS bug with writes from setuid programs; this breaks RCS

Linux 2.0.x NFS bug with writes from setuid programs; this breaks RCS

Post by Paul Egge » Wed, 20 Aug 1997 04:00:00



RCS 5.7 doesn't work in Linux when it's run setuid and the RCS files
are accessed via NFS.  I've narrowed down the problem to a Linux
kernel bug when a setuid client accesses NFS files.

Here's the bug.  A `write' system call fails on a file descriptor that
is validly open for writing if the effective user does not have write
permission to that file.  The `write' should succeed, since once you've
successfully opened a file for writing you should be able to write to
it regardless of the file's current permissions.

Here's a small test program that illustrates the bug.  The program
should create a readonly file with two newlines in it, but under Linux
if the file is accessed via NFS, the program reports an error while
attempting to write the second newline.  I've reproduced the problem
under Linux 2.0.27 (Red Hat) where the NFS server is Solaris 2.5.1
(sparc).  From the source in fs/nfs, it looks like the bug is still
extant in 2.0.30.

/* Run this test as follows:

   U=some_other_user
   D=some_NFS_directory_that_user_U_can_read_and_write
   cc test1.c -o test1
   su root -c "chown $U test1 && chmod 4755 test1"
   rm -f $D/output
   ./test1 $D/output

   If you have the bug, then the program will respond:

   write as effective user: Permission denied

*/

#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

static char const *test(char const *outputfile)
{
  int e = geteuid();
  int r = getuid();
  int f;

  if (e == 0)
    return "euid is root!  Use some other uid.";
  if (r == 0)
    return "uid is root!  Use some other uid.";
  if (e == r)
    return "euid == uid!  Must be setuid to illustrate the bug.";
  if (seteuid(r) != 0)
    return "seteuid(r)";
  f = open(outputfile, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL, 0444);
  if (f < 0)
    return "open";
  if (write(f, "\n", 1) != 1)
    return "write as real user";
  if (seteuid(e) != 0)
    return "seteuid(e)";
  if (write(f, "\n", 1) != 1)
    return "write as effective user";
  return 0;

Quote:}

int main(int argc, char **argv)
{
  if (argc != 2) {
    fprintf (stderr, "%s: usage: %s outputfile\n", argv[0], argv[0]);
    return 1;
  } else {
    char const *s = test(argv[1]);
    if (s)
      perror(s);
    return s != 0;
  }
Quote:}