]> git.ipfire.org Git - thirdparty/coreutils.git/commitdiff
.
authorJim Meyering <jim@meyering.net>
Mon, 19 Dec 2005 18:13:00 +0000 (18:13 +0000)
committerJim Meyering <jim@meyering.net>
Mon, 19 Dec 2005 18:13:00 +0000 (18:13 +0000)
lib/chmod-safer.c [new file with mode: 0644]
lib/chmod-safer.h [new file with mode: 0644]

diff --git a/lib/chmod-safer.c b/lib/chmod-safer.c
new file mode 100644 (file)
index 0000000..995efdc
--- /dev/null
@@ -0,0 +1,118 @@
+/* like chmod(2), but safer, if possible
+   Copyright (C) 2005 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
+
+/* written by Jim Meyering */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "chmod-safer.h"
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <assert.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "fcntl--.h" /* for the open->open_safer mapping */
+
+#ifndef HAVE_FCHMOD
+# define HAVE_FCHMOD false
+# define fchmod(fd, mode) (-1)
+#endif
+
+/* Return true if the file corresponding to stat buffer pointer ST is of type,
+   TYPE, and if its major and minor device numbers match (if appropriate:
+   when the type is S_IFIFO or S_IFBLK).  */
+static inline bool
+same_file_type (struct stat const *st, dev_t device, mode_t type)
+{
+  /* The types must always match.  */
+  if ( ! (st->st_mode & type))
+    return false;
+
+  /* For character and block devices, the major and minor device
+     numbers must match, too.  */
+  if (type & (S_IFCHR | S_IFBLK))
+    return st->st_rdev == device;
+
+  return true;
+}
+
+/* Assuming we can use open-with-O_NOFOLLOW and fchmod, set the permissions
+   of FILE to MODE, using fchmod -- but fail (setting errno to EACCES) if
+   anyone hard-links to FILE, replaces it with a symlink, or otherwise
+   changes its type.  Return zero upon success.  */
+static int
+fchmod_new (char const *file, mode_t mode, dev_t device, mode_t file_type)
+{
+  int fail = 1;
+  struct stat sb;
+  int saved_errno = 0;
+  int fd = open (file, O_NOFOLLOW | O_RDONLY | O_NDELAY);
+
+  assert (O_NOFOLLOW);
+
+  if (0 <= fd
+      && fstat (fd, &sb) == 0
+      /* Given the entry we've just created, if its link count is
+        not 1 or its type/device has changed, then someone may be
+        trying to do something nasty.  However, the risk of such an
+        attack is so low that it isn't worth a special diagnostic.
+        Simply skip the fchmod and set errno, so that the
+        code below reports the failure to set permissions.
+        Note that we don't check the link count if the expected
+        type is `directory'.  */
+      && (((sb.st_nlink == 1 || file_type == S_IFDIR)
+          && same_file_type (&sb, device, file_type))
+         || ((errno = EACCES), 0))
+      && fchmod (fd, mode) == 0)
+    {
+      fail = 0;
+    }
+  else
+    {
+      saved_errno = errno;
+    }
+
+  if (close (fd) != 0 && saved_errno == 0)
+    saved_errno = errno;
+
+  errno = saved_errno;
+  return fail;
+}
+
+/* Use a safer variant of chmod, if the underlying system facilities permit.
+   This can avoid a minor race condition between when a file/device/directory
+   is created and when we set its permissions.  If the system provides an
+   fchmod function and if its open syscall honors the O_NOFOLLOW flag, then
+   use open,fchmod,close.  Otherwise, just call chmod.  FILE and MODE are
+   just as for chmod.  DEVICE is the combination (a la stat.st_rdev) of major
+   and minor device numbers.  FILE_TYPE is one of the S_IF* values, e.g.,
+   S_IFBLK, S_IFCHR, etc. */
+int
+chmod_safer (char const *file, mode_t mode, dev_t device, mode_t file_type)
+{
+  /* Using open and fchmod is reliable only if open honors the O_NOFOLLOW
+     flag.  Otherwise, an attacker could simply replace the just-created
+     entry with a symlink, and open would follow it blindly.  */
+  return (HAVE_FCHMOD && O_NOFOLLOW
+         ? fchmod_new (file, mode, device, file_type)
+         : chmod      (file, mode));
+}
diff --git a/lib/chmod-safer.h b/lib/chmod-safer.h
new file mode 100644 (file)
index 0000000..e078af0
--- /dev/null
@@ -0,0 +1,28 @@
+/* like chmod(2), but safer, if possible
+   Copyright (C) 2005 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
+
+/* Written by Jim Meyering.  */
+
+#ifndef FCHMOD_SAFER_H
+# define FCHMOD_SAFER_H 1
+
+# include <sys/types.h>
+# include <sys/stat.h>
+
+int chmod_safer (char const *file, mode_t mode, dev_t device, mode_t file_type);
+
+#endif /* FCHMOD_SAFER_H */