#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: */
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)
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);
}
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("/"))