Leave '/var/lib/sandbox/sandbox-server-$$' directory locked.
authorshort <>
Thu, 21 Aug 2003 16:06:25 +0000 (16:06 +0000)
committershort <>
Thu, 21 Aug 2003 16:06:25 +0000 (16:06 +0000)
Delete all unlocked directories in '/var/lib/sandbox/'
Security: chrooted_unlink_recursive(): Protect removal against enemy symlinks.

src/client/sandbox-server/main.c

index cab66c7..14e294f 100644 (file)
@@ -38,6 +38,8 @@
 #include "../../libcaptive/sandbox/split.h"    /* for captive_sandbox_fd_closeup(); FIXME */
 #include <grp.h>
 #include <pwd.h>
+#include <fcntl.h>
+#include <sys/file.h>
 
 
 /* CONFIG: */
@@ -144,30 +146,33 @@ int linkbuflen;
        depth--;
 }
 
-static void unlink_recursive_suidsafe(const gchar *pathname)
+static void chrooted_unlink_recursive(const gchar *pathname)
 {
 DIR *dir;
 struct dirent *dirent;
 static gint depth=0;
+struct stat statbuf;
 
        if (++depth>=1000)
-               fatal("Loop count >=%d during unlink_recursive_suidsafe(\"%s\")",depth,pathname);
+               fatal("Loop count >=%d during chrooted_unlink_recursive(\"%s\")",depth,pathname);
 
-       if (!(dir=opendir(pathname))) {
-               if (errno!=ENOTDIR)
-                       fatal("Cannot opendir(\"%s\") to delete leftover sandbox files: %m",pathname);
-               /* errno==ENOTDIR, a regular file */
+       /* Security: Do not allow anyone to escape the sandbox directory by symlinks. */
+       if (lstat(pathname,&statbuf))
+               fatal("Cannot lstat(\"%s\") to delete leftover sandbox files: %m",pathname);
+       if (!S_ISDIR(statbuf.st_mode)) {
                if (unlink(pathname))
-                       fatal("Cannot pathname(\"%s\") to delete leftover sandbox files: %m",pathname);
+                       fatal("Cannot unlink(\"%s\") to delete leftover sandbox files: %m",pathname);
                goto done;
                }
+       if (!(dir=opendir(pathname)))
+               fatal("Cannot opendir(\"%s\") to delete leftover sandbox files: %m",pathname);
        while (errno=0,(dirent=readdir(dir))) {
 gchar *dirent_path;
 
                if (!strcmp(dirent->d_name,".") || !strcmp(dirent->d_name,".."))
                        continue;
                dirent_path=g_strdup_printf("%s/%s",pathname,dirent->d_name);
-               unlink_recursive_suidsafe(dirent_path);
+               chrooted_unlink_recursive(dirent_path);
                g_free(dirent_path);
                }
        if (errno)
@@ -181,14 +186,84 @@ done:
        depth--;
 }
 
-static void chrooted_createdir(const gchar *dir,uid_t uid,gid_t gid)
+static void chrooted_cleanuplockeddirs(const gchar *pathname)
+{
+DIR *dir;
+struct dirent *dirent;
+
+       if (!(dir=opendir(pathname))) {
+               if (errno!=ENOTDIR)
+                       fatal("Cannot opendir(\"%s\") to delete leftover sandbox files: %m",pathname);
+               /* errno==ENOTDIR, a regular file */
+               if (unlink(pathname))
+                       fatal("Cannot unlink(\"%s\") to delete leftover sandbox files: %m",pathname);
+               return;
+               }
+       while (errno=0,(dirent=readdir(dir))) {
+gchar *dirent_path;
+int direntfd;
+
+               if (!strcmp(dirent->d_name,".") || !strcmp(dirent->d_name,".."))
+                       continue;
+               dirent_path=g_strdup_printf("%s/%s",pathname,dirent->d_name);
+               if (-1==(direntfd=open(dirent_path,O_RDONLY))) {
+                       if (errno==ENOENT)      /* It could disappear in the meantime. */
+                               goto next_dirent_free_dirent_path;
+                       fatal("Cannot open(\"%s\") as the child directory during delete of leftover sandbox files: %m",dirent_path);
+                       }
+               if (flock(direntfd,LOCK_EX|LOCK_NB)) {
+                       if (errno==EWOULDBLOCK) /* Valid directory in use. */
+                               goto next_dirent_close_direntfd;
+                       fatal("Cannot flock(\"%s\",LOCK_EX|LOCK_NB) child directory during delete of leftover sandbox files: %m",dirent_path);
+                       }
+               chrooted_unlink_recursive(dirent_path);
+next_dirent_close_direntfd:
+               if (close(direntfd))
+                       fatal("Cannot close(\"%s\") child directory during delete of leftover sandbox files: %m",dirent_path);
+next_dirent_free_dirent_path:
+               g_free(dirent_path);
+               }
+       if (errno)
+               fatal("Cannot readdir(\"%s\") during delete of leftover sandbox files: %m",pathname);
+       if (closedir(dir))
+               fatal("Cannot closedir(\"%s\") during delete of leftover sandbox files: %m",pathname);
+}
+
+static void chrooted_createdir(const gchar *dir,uid_t uid,gid_t gid,gboolean lock)
 {
-       if (mkdir(dir,0711)) {
-               if (errno!=EEXIST)
-                       fatal("Failed to create chroot directory \"%s\": %m",dir);
-               unlink_recursive_suidsafe(dir);
-               if (mkdir(dir,0711))
-                       fatal("Failed to create chroot directory \"%s\" after attempted unlink: %m",dir);
+gint retries;
+
+       for (retries=0;retries<10;retries++) {
+struct stat statbuf;
+int dirfd;
+
+               if (mkdir(dir,0711)) {
+                       if (errno!=EEXIST)
+                               fatal("Failed to create chroot directory \"%s\": %m",dir);
+                       chrooted_unlink_recursive(dir);
+                       if (mkdir(dir,0711))
+                               fatal("Failed to create chroot directory \"%s\" after attempted unlink: %m",dir);
+                       }
+               if (!lock)
+                       break;
+               dirfd=open(dir,O_RDONLY);
+               if (dirfd==-1) {
+                       if (errno!=ENOENT)
+                               fatal("Failed to open created chroot directory \"%s\" to lock it: %m",dir);
+                       continue;
+                       }
+               /* Do not use 'LOCK_NB' here as the garbage collector should release it soon. */
+               if (flock(dirfd,LOCK_EX))
+                       fatal("Failed to lock created chroot directory \"%s\": %m",dir);
+               if (lstat(dir,&statbuf)) {
+                       if (errno!=ENOENT)
+                               fatal("Failed to lstat(2) created chroot directory \"%s\": %m",dir);
+                       if (close(dirfd))
+                               fatal("Failed to close created and locked chroot directory \"%s\": %m",dir);
+                       continue;
+                       }
+               /* Leave 'dirfd' open to leave it LOCK_EX-ed. */
+               break;
                }
        if (chown(dir,uid,gid))
                fatal("Failed to chown(\"%s\",%d,%d): %m",dir,uid,gid);
@@ -280,10 +355,14 @@ gint gi;
                        }
                g_rand_free(grand);
                *s=0;
+               if (geteuid()==0)       /* Not 'fragile' as we can be native 'root'. */
+                       chrooted_cleanuplockeddirs(optarg_chroot);
                chroot_pid_dir=captive_printf_alloca("%s/sandbox-server-%d",optarg_chroot,(int)getpid());
-               chrooted_createdir(chroot_pid_dir,(!optarg_setuid ? (uid_t)-1 : want_uid),(!optarg_setgid ? (gid_t)-1 : want_gid));
+               chrooted_createdir(chroot_pid_dir,(!optarg_setuid ? (uid_t)-1 : want_uid),(!optarg_setgid ? (gid_t)-1 : want_gid),
+                               TRUE);  /* lock */
                chroot_pid_hashkey_dir=captive_printf_alloca("%s/%s",chroot_pid_dir,chroot_hashkey);
-               chrooted_createdir(chroot_pid_hashkey_dir,(!optarg_setuid ? (uid_t)-1 : want_uid),(!optarg_setgid ? (gid_t)-1 : want_gid));
+               chrooted_createdir(chroot_pid_hashkey_dir,(!optarg_setuid ? (uid_t)-1 : want_uid),(!optarg_setgid ? (gid_t)-1 : want_gid),
+                               FALSE); /* lock */
                if (chroot(chroot_pid_hashkey_dir))
                        fatal("Failed to chroot(\"%s\"): %m",chroot_pid_hashkey_dir);
                if (chdir("/"))