static int merge_dirs (const char *root, char **dirs, int n_dirs) { DIR *dir; char *subdirs[n_dirs]; struct dirent *dirent; struct stat st; char *src_path; char *dest_path; int conflict; int i, j; for (i = 0; i < n_dirs; i++) { if (dirs[i] == NULL) continue; dir = opendir (dirs[i]); if (dir == NULL) continue; while ((dirent = readdir (dir)) != NULL) { src_path = strconcat (dirs[i], "/", dirent->d_name); if (strcmp (dirent->d_name, ".") == 0 || strcmp (dirent->d_name, "..") == 0) continue; dest_path = strconcat (root, "/", dirent->d_name); if (lstat (dest_path, &st) == 0) { free (dest_path); continue; /* We already copyed this file */ } if (lstat (src_path, &st) < 0) { free (dest_path); continue; } if (S_ISCHR (st.st_mode) || S_ISBLK (st.st_mode) || S_ISFIFO (st.st_mode) || S_ISSOCK (st.st_mode)) { fprintf (stderr, "WARNING: ignoring special file %s\n", src_path); free (dest_path); continue; } conflict = has_conflict (dirs, n_dirs, dirent->d_name, i); if (conflict == NO_CONFLICTS) { bind_file (src_path, dest_path, &st); } else if (conflict == DIR_CONFLICT) { if (mkdir (dest_path, st.st_mode & 0777)) fatal_errno ("create merged dir"); if (lchown(dest_path, st.st_uid, st.st_gid) < 0) fatal_errno ("lchown"); for (j = 0; j < n_dirs; j++) subdirs[j] = get_subdir (dirs[j], dirent->d_name); merge_dirs (dest_path, subdirs, n_dirs); for (j = 0; j < n_dirs; j++) { if (subdirs[j]) free (subdirs[j]); } } else fatal ("Filename conflicts, refusing to mount\n"); free (dest_path); } } return 0; }
static void perform_merge(int op) { #ifndef _WIN32 struct stat src, dst; #endif ops_t *ops; create_empty_dir("first"); create_empty_dir("first/nested1"); create_empty_dir("first/nested1/nested2"); create_empty_file("first/nested1/nested2/file"); create_empty_dir("second"); create_empty_dir("second/nested1"); #ifndef _WIN32 /* Something about GNU Hurd and OS X differs, so skip this workaround there. * Really need to figure out what's wrong with this thing... */ #if !defined(__gnu_hurd__) && !defined(__APPLE__) { struct timeval tv[2]; gettimeofday(&tv[0], NULL); tv[1] = tv[0]; /* This might be Linux-specific, but for the test to work properly access * time should be newer than modification time, in which case it's not * changed on listing directory. */ tv[0].tv_sec += 3; tv[0].tv_usec += 4; tv[1].tv_sec += 1; tv[1].tv_usec += 2; utimes("first/nested1", tv); } #endif assert_success(chmod("first/nested1", 0700)); assert_success(os_stat("first/nested1", &src)); #endif cmd_group_begin("undo msg"); assert_non_null(ops = ops_alloc(op, 0, "merge", ".", ".")); ops->crp = CRP_OVERWRITE_ALL; if(op == OP_MOVEF) { assert_success(merge_dirs("first", "second", ops)); } else { #ifndef _WIN32 if(!cfg.use_system_calls) { assert_success( perform_operation(op, ops, NULL, "first/nested1", "second/")); } else #endif { assert_success(perform_operation(op, ops, NULL, "first", "second")); } } ops_free(ops); cmd_group_end(); #ifndef _WIN32 { assert_success(os_stat("second/nested1", &dst)); #ifndef HAVE_STRUCT_STAT_ST_MTIM #define st_atim st_atime #define st_mtim st_mtime #endif assert_success(memcmp(&src.st_atim, &dst.st_atim, sizeof(src.st_atim))); assert_success(memcmp(&src.st_mtim, &dst.st_mtim, sizeof(src.st_mtim))); assert_success(memcmp(&src.st_mode, &dst.st_mode, sizeof(src.st_mode))); } #endif assert_true(file_exists("second/nested1/nested2/file")); assert_success(unlink("second/nested1/nested2/file")); assert_success(rmdir("second/nested1/nested2")); assert_success(rmdir("second/nested1")); assert_success(rmdir("second")); }
int main (int argc, char **argv) { char tempdir[] = "/tmp/approot_XXXXXX"; char *base_os; char **images; char *root; int n_images; pid_t child; int child_status = 0; char *app_root; char **mountpoints; int n_mountpoints; int i; uid_t ruid, euid, suid; gid_t rgid, egid, sgid; char cwd_buf[PATH_MAX]; char *cwd; if (argc < 2) fatal ("Too few arguments, need base and at least one image"); base_os = argv[1]; images = &argv[2]; n_images = argc - 2; root = mkdtemp (tempdir); if (root == NULL) fatal ("Can't create root"); if (getresgid (&rgid, &egid, &sgid) < 0) fatal_errno ("getresgid"); if (getresuid (&ruid, &euid, &suid) < 0) fatal_errno ("getresuid"); if ((child = syscall (__NR_clone, SIGCHLD | CLONE_NEWNS, NULL)) < 0) fatal_errno ("clone"); if (child == 0) { /* Child */ /* Disable setuid, new caps etc for children */ if (prctl (PR_SET_NO_NEW_PRIVS, 1) < 0 && errno != EINVAL) fatal_errno ("prctl (PR_SET_NO_NEW_PRIVS)"); else if (prctl (PR_SET_SECUREBITS, SECBIT_NOROOT | SECBIT_NOROOT_LOCKED) < 0) fatal_errno ("prctl (SECBIT_NOROOT)"); /* Don't leak our mounts to the parent namespace */ if (mount (NULL, "/", "none", MS_SLAVE | MS_REC, NULL) < 0) fatal_errno ("mount(/, MS_SLAVE | MS_REC)"); /* Check we're allowed to chdir into base os */ cwd = getcwd (cwd_buf, sizeof (cwd_buf)); if (fsuid_chdir (ruid, base_os) < 0) fatal_errno ("chdir"); if (chdir (cwd) < 0) fatal_errno ("chdir"); if (mount ("tmpfs", root, "tmpfs", MS_MGC_VAL | MS_PRIVATE, NULL) != 0) fatal_errno ("execv"); n_mountpoints = n_images + 1; mountpoints = calloc (n_mountpoints, sizeof (char *)); if (mountpoints == NULL) fatal ("oom"); mountpoints[0] = base_os; for (i = 0; i < n_images; i++) { if (fsuid_access (ruid, images[i], R_OK) < 0) fatal_errno ("access"); mountpoints[i+1] = mount_image (root, images[i]); if (mountpoints[i+1] == NULL) fatal ("mount image %s\n", images[i]); } app_root = make_fs_dir (root, "/root", 0555); if (app_root == NULL) fatal ("make_fs_dir root"); setup_base (app_root); merge_dirs (app_root, mountpoints, n_mountpoints); if (chdir (app_root) < 0) fatal_errno ("chdir"); if (chroot (".") < 0) fatal_errno ("chroot"); /* Switch back to the uid of our invoking process. These calls are * irrevocable - see setuid(2) */ if (setgid (rgid) < 0) fatal_errno ("setgid"); if (setuid (ruid) < 0) fatal_errno ("setuid"); if (execl ("/bin/sh", "/bin/sh", NULL) < 0) fatal_errno ("execl"); } /* Parent */ /* Let's also setuid back in the parent - there's no reason to stay uid 0, and * it's just better to drop privileges. */ if (setgid (rgid) < 0) fatal_errno ("setgid"); if (setuid (ruid) < 0) fatal_errno ("setuid"); if (child == -1) fatal_errno ("clone"); /* Ignore Ctrl-C in parent while waiting */ signal (SIGINT, SIG_IGN); if (waitpid (child, &child_status, 0) < 0) fatal_errno ("waitpid"); rmdir (root); if (WIFEXITED (child_status)) return WEXITSTATUS (child_status); else return 1; }