diff --git a/direct.c b/direct.c index 73d44b4..a25c6b3 100644 --- a/direct.c +++ b/direct.c @@ -34,9 +34,6 @@ #include "utils.h" -#define XATTR_OVERRIDE_STAT "user.fuseoverlayfs.override_stat" -#define XATTR_PRIVILEGED_OVERRIDE_STAT "security.fuseoverlayfs.override_stat" - static int direct_file_exists (struct ovl_layer *l, const char *pathname) { @@ -79,64 +76,6 @@ direct_getxattr (struct ovl_layer *l, const char *path, const char *name, char * return lgetxattr (full_path, name, buf, size); } -static int -override_mode (struct ovl_layer *l, int fd, const char *path, struct stat *st) -{ - int ret; - uid_t uid; - gid_t gid; - mode_t mode; - char buf[64]; - cleanup_close int cleanup_fd = -1; - const char *xattr_name; - - if (l->has_stat_override == 0 && l->has_privileged_stat_override == 0) - return 0; - - xattr_name = l->has_privileged_stat_override ? XATTR_PRIVILEGED_OVERRIDE_STAT : XATTR_OVERRIDE_STAT; - - if (fd >= 0) - { - ret = fgetxattr (fd, xattr_name, buf, sizeof (buf) - 1); - if (ret < 0) - return ret; - } - else - { - char full_path[PATH_MAX]; - - full_path[0] = '\0'; - ret = open_fd_or_get_path (l, path, full_path, &cleanup_fd, O_RDONLY); - if (ret < 0) - return ret; - fd = cleanup_fd; - - if (fd >= 0) - ret = fgetxattr (fd, xattr_name, buf, sizeof (buf) - 1); - else - ret = lgetxattr (full_path, xattr_name, buf, sizeof (buf) - 1); - - if (ret < 0) - return ret; - } - - buf[ret] = '\0'; - - ret = sscanf (buf, "%d:%d:%o", &uid, &gid, &mode); - if (ret != 3) - { - errno = EINVAL; - return -1; - } - - st->st_uid = uid; - st->st_gid = gid; - st->st_mode = (st->st_mode & S_IFMT) | mode; - - return 0; -} - - static int direct_fstat (struct ovl_layer *l, int fd, const char *path, unsigned int mask, struct stat *st) { @@ -151,7 +90,7 @@ direct_fstat (struct ovl_layer *l, int fd, const char *path, unsigned int mask, if (ret == 0) { statx_to_stat (&stx, st); - return override_mode (l, fd, path, st); + return override_mode (l, fd, NULL, path, st); } return ret; @@ -162,7 +101,7 @@ direct_fstat (struct ovl_layer *l, int fd, const char *path, unsigned int mask, if (ret != 0) return ret; - return override_mode (l, fd, path, st); + return override_mode (l, fd, NULL, path, st); } static int @@ -179,7 +118,7 @@ direct_statat (struct ovl_layer *l, const char *path, struct stat *st, int flags if (ret == 0) { statx_to_stat (&stx, st); - return override_mode (l, -1, path, st); + return override_mode (l, -1, NULL, path, st); } return ret; @@ -189,7 +128,7 @@ direct_statat (struct ovl_layer *l, const char *path, struct stat *st, int flags if (ret != 0) return ret; - return override_mode (l, -1, path, st); + return override_mode (l, -1, NULL, path, st); } static struct dirent * @@ -274,6 +213,12 @@ direct_num_of_layers (const char *opaque, const char *path) return 1; } +static bool +direct_must_be_remapped (struct ovl_layer *l) +{ + return l->has_privileged_stat_override == 0 && l->has_stat_override == 0; +} + struct data_source direct_access_ds = { .num_of_layers = direct_num_of_layers, @@ -289,4 +234,5 @@ struct data_source direct_access_ds = .getxattr = direct_getxattr, .listxattr = direct_listxattr, .readlinkat = direct_readlinkat, + .must_be_remapped = direct_must_be_remapped, }; diff --git a/fuse-overlayfs.h b/fuse-overlayfs.h index 4b5662b..d599fbd 100644 --- a/fuse-overlayfs.h +++ b/fuse-overlayfs.h @@ -94,6 +94,7 @@ struct ovl_data int fast_ino_check; int writeback; int disable_xattrs; + int xattr_permissions; /* current uid/gid*/ uid_t uid; @@ -132,6 +133,7 @@ struct data_source int (*listxattr)(struct ovl_layer *l, const char *path, char *buf, size_t size); int (*getxattr)(struct ovl_layer *l, const char *path, const char *name, char *buf, size_t size); ssize_t (*readlinkat)(struct ovl_layer *l, const char *path, char *buf, size_t bufsiz); + bool (*must_be_remapped)(struct ovl_layer *l); }; /* passtrough to the file system. */ diff --git a/main.c b/main.c index 680063f..d4553de 100644 --- a/main.c +++ b/main.c @@ -203,6 +203,8 @@ static const struct fuse_opt ovl_opts[] = { offsetof (struct ovl_data, disable_xattrs), 1}, {"plugins=%s", offsetof (struct ovl_data, plugins), 0}, + {"xattr_permissions=%d", + offsetof (struct ovl_data, xattr_permissions), 0}, FUSE_OPT_END }; @@ -275,7 +277,6 @@ check_can_mknod (struct ovl_data *lo) can_mknod = false; } - static struct ovl_mapping * read_mappings (const char *str) { @@ -465,6 +466,139 @@ can_access_xattr (const char *name) && !has_prefix (name, PRIVILEGED_XATTR_PREFIX); } +static ssize_t +write_permission_xattr (struct ovl_data *lo, int fd, const char *path, uid_t uid, gid_t gid, mode_t mode) +{ + char buf[64]; + size_t len; + int ret; + const char *name = NULL; + + switch (lo->xattr_permissions) + { + case 0: + return 0; + + case 1: + name = XATTR_PRIVILEGED_OVERRIDE_STAT; + break; + + case 2: + name = XATTR_OVERRIDE_STAT; + break; + + default: + errno = EINVAL; + return -1; + } + + if (path == NULL && fd < 0) + { + errno = EINVAL; + return -1; + } + + len = sprintf (buf, "%d:%d:%o", uid, gid, mode); + if (fd >= 0) + return fsetxattr (fd, name, buf, len, 0); + + ret = lsetxattr (path, name, buf, len, 0); + /* Ignore EPERM in unprivileged mode. */ + if (ret < 0 && lo->xattr_permissions == 2 && errno == EPERM) + return 0; + return ret; +} + +static int +do_fchown (struct ovl_data *lo, int fd, uid_t uid, gid_t gid, mode_t mode) +{ + if (lo->xattr_permissions) + return write_permission_xattr (lo, fd, NULL, uid, gid, mode); + return fchown (fd, uid, gid); +} +/* Make sure it is not used anymore. */ +#define fchown ERROR + +static int +do_chown (struct ovl_data *lo, const char *path, uid_t uid, gid_t gid, mode_t mode) +{ + if (lo->xattr_permissions) + return write_permission_xattr (lo, -1, path, uid, gid, mode); + return chown (path, uid, gid); +} +/* Make sure it is not used anymore. */ +#define chown ERROR + +static int +do_fchownat (struct ovl_data *lo, int dfd, const char *path, uid_t uid, gid_t gid, mode_t mode, int flags) +{ + if (lo->xattr_permissions) + { + char proc_path[32]; + cleanup_close int fd = openat (dfd, path, O_NOFOLLOW|O_PATH); + if (fd < 0) + return fd; + + sprintf (proc_path, "/proc/self/fd/%d", fd); + return write_permission_xattr (lo, -1, proc_path, uid, gid, mode); + } + return fchownat (dfd, path, uid, gid, 0); +} +/* Make sure it is not used anymore. */ +#define fchownat ERROR + +static int +do_fchmod (struct ovl_data *lo, int fd, mode_t mode) +{ + if (lo->xattr_permissions) + { + struct ovl_layer *upper = get_upper_layer (lo); + struct stat st; + + if (upper == NULL) + { + errno = EROFS; + return -1; + } + + st.st_uid = 0; + st.st_gid = 0; + if (override_mode (upper, fd, NULL, NULL, &st) < 0 && errno != ENODATA) + return -1; + + return write_permission_xattr (lo, fd, NULL, st.st_uid, st.st_gid, mode); + } + return fchmod (fd, mode); +} +/* Make sure it is not used anymore. */ +#define fchmod ERROR + +static int +do_chmod (struct ovl_data *lo, const char *path, mode_t mode) +{ + if (lo->xattr_permissions) + { + struct ovl_layer *upper = get_upper_layer (lo); + struct stat st; + + if (upper == NULL) + { + errno = EROFS; + return -1; + } + + st.st_uid = 0; + st.st_gid = 0; + if (override_mode (upper, -1, path, NULL, &st) < 0 && errno != ENODATA) + return -1; + + return write_permission_xattr (lo, -1, path, st.st_uid, st.st_gid, mode); + } + return chmod (path, mode); +} +/* Make sure it is not used anymore. */ +#define chmod ERROR + static int set_fd_origin (int fd, const char *origin) { @@ -698,8 +832,11 @@ rpl_stat (fuse_req_t req, struct ovl_node *node, int fd, const char *path, struc if (ret < 0) return ret; - st->st_uid = find_mapping (st->st_uid, data->uid_mappings, true, true); - st->st_gid = find_mapping (st->st_gid, data->gid_mappings, true, false); + if (l->ds->must_be_remapped && l->ds->must_be_remapped (l)) + { + st->st_uid = find_mapping (st->st_uid, data->uid_mappings, true, true); + st->st_gid = find_mapping (st->st_gid, data->gid_mappings, true, false); + } st->st_ino = node->tmp_ino; st->st_dev = node->tmp_dev; @@ -2497,9 +2634,9 @@ create_directory (struct ovl_data *lo, int dirfd, const char *name, const struct if (ret < 0) goto out; - if (uid != lo->uid || gid != lo->gid) + if (uid != lo->uid || gid != lo->gid || get_upper_layer (lo)->has_stat_override || get_upper_layer (lo)->has_privileged_stat_override) { - ret = fchown (dfd, uid, gid); + ret = do_fchown (lo, dfd, uid, gid, mode); if (ret < 0) goto out; } @@ -2721,9 +2858,9 @@ copyup (struct ovl_data *lo, struct ovl_node *node) if (dfd < 0) goto exit; - if (st.st_uid != lo->uid || st.st_gid != lo->gid) + if (st.st_uid != lo->uid || st.st_gid != lo->gid || get_upper_layer (lo)->has_stat_override || get_upper_layer (lo)->has_privileged_stat_override) { - ret = fchown (dfd, st.st_uid, st.st_gid); + ret = do_fchown (lo, dfd, st.st_uid, st.st_gid, st.st_mode); if (ret < 0) goto exit; } @@ -3177,7 +3314,7 @@ direct_create_file (struct ovl_layer *l, int dirfd, const char *path, uid_t uid, int ret; /* try to create directly the file if it doesn't need to be chowned. */ - if (uid == lo->uid && gid == lo->gid) + if (uid == lo->uid && gid == lo->gid && !l->has_stat_override && !l->has_privileged_stat_override) { ret = TEMP_FAILURE_RETRY (safe_openat (get_upper_layer (lo)->fd, path, flags, mode)); if (ret >= 0) @@ -3191,9 +3328,9 @@ direct_create_file (struct ovl_layer *l, int dirfd, const char *path, uid_t uid, fd = TEMP_FAILURE_RETRY (safe_openat (lo->workdir_fd, wd_tmp_file_name, flags, mode)); if (fd < 0) return -1; - if (uid != lo->uid || gid != lo->gid) + if (uid != lo->uid || gid != lo->gid || l->has_stat_override || l->has_privileged_stat_override) { - if (fchown (fd, uid, gid) < 0) + if (do_fchown (lo, fd, uid, gid, mode) < 0) { unlinkat (lo->workdir_fd, wd_tmp_file_name, 0); return -1; @@ -3400,7 +3537,7 @@ ovl_write_buf (fuse_req_t req, fuse_ino_t ino, /* if it is a writepage request, make sure to restore the setuid bit. */ if (fi->writepage && (inode->mode & (S_ISUID|S_ISGID))) { - if (fchmod (fi->fh, inode->mode) < 0) + if (do_fchmod (lo, fi->fh, inode->mode) < 0) { fuse_reply_err (req, errno); return; @@ -3665,9 +3802,9 @@ ovl_setattr (fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, stru if (to_set & FUSE_SET_ATTR_MODE) { if (fd >= 0) - ret = fchmod (fd, attr->st_mode); + ret = do_fchmod (lo, fd, attr->st_mode); else - ret = chmod (path, attr->st_mode); + ret = do_chmod (lo, path, attr->st_mode); if (ret < 0) { fuse_reply_err (req, errno); @@ -3692,9 +3829,9 @@ ovl_setattr (fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, stru if (uid != -1 || gid != -1) { if (fd >= 0) - ret = fchown (fd, uid, gid); + ret = do_fchown (lo, fd, uid, gid, node->ino->mode); else - ret = chown (path, uid, gid); + ret = do_chown (lo, path, uid, gid, node->ino->mode); if (ret < 0) { fuse_reply_err (req, errno); @@ -3834,9 +3971,9 @@ direct_symlinkat (struct ovl_layer *l, const char *target, const char *linkpath, if (ret < 0) return ret; - if (uid != lo->uid || gid != lo->gid) + if (uid != lo->uid || gid != lo->gid || l->has_stat_override || l->has_privileged_stat_override) { - ret = fchownat (lo->workdir_fd, wd_tmp_file_name, uid, gid, AT_SYMLINK_NOFOLLOW); + ret = do_fchownat (lo, lo->workdir_fd, wd_tmp_file_name, uid, gid, AT_SYMLINK_NOFOLLOW, 0755); if (ret < 0) { unlinkat (lo->workdir_fd, wd_tmp_file_name, 0); @@ -4414,6 +4551,8 @@ ovl_mknod (fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev fprintf (stderr, "ovl_mknod(ino=%" PRIu64 ", name=%s, mode=%d, rdev=%lu)\n", parent, name, mode, rdev); + mode = mode & ~ctx->umask; + node = do_lookup_file (lo, parent, name); if (node != NULL && !node->whiteout) { @@ -4435,14 +4574,14 @@ ovl_mknod (fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev return; } sprintf (wd_tmp_file_name, "%lu", get_next_wd_counter ()); - ret = mknodat (lo->workdir_fd, wd_tmp_file_name, mode & ~ctx->umask, rdev); + ret = mknodat (lo->workdir_fd, wd_tmp_file_name, mode, rdev); if (ret < 0) { fuse_reply_err (req, errno); return; } - if (fchownat (lo->workdir_fd, wd_tmp_file_name, get_uid (lo, ctx->uid), get_gid (lo, ctx->gid), 0) < 0) + if (do_fchownat (lo, lo->workdir_fd, wd_tmp_file_name, get_uid (lo, ctx->uid), get_gid (lo, ctx->gid), 0, mode) < 0) { fuse_reply_err (req, errno); unlinkat (lo->workdir_fd, wd_tmp_file_name, 0); @@ -5101,6 +5240,7 @@ main (int argc, char *argv[]) .redirect_dir = NULL, .mountpoint = NULL, .fsync = 1, + .xattr_permissions = 0, .timeout = 1000000000.0, .timeout_str = NULL, .writeback = 1, @@ -5210,8 +5350,51 @@ main (int argc, char *argv[]) error (EXIT_FAILURE, errno, "cannot read upper dir"); layers = tmp_layer; } + lo.layers = layers; + if (lo.upperdir) + { + if (lo.xattr_permissions) + { + const char *name = NULL; + char data[64]; + ssize_t s; + if (lo.xattr_permissions == 1) + { + get_upper_layer (&lo)->has_privileged_stat_override = 1; + name = XATTR_PRIVILEGED_OVERRIDE_STAT; + } + else if (lo.xattr_permissions == 2) + { + get_upper_layer (&lo)->has_stat_override = 1; + name = XATTR_OVERRIDE_STAT; + } + else + error (EXIT_FAILURE, 0, "invalid value for xattr_permissions"); + + s = fgetxattr (get_upper_layer (&lo)->fd, name, data, sizeof (data)); + if (s < 0) + { + if (errno != ENODATA) + error (EXIT_FAILURE, errno, "read xattr `%s` from upperdir", name); + else + { + struct stat st; + ret = fstat (get_upper_layer (&lo)->fd, &st); + if (ret < 0) + error (EXIT_FAILURE, errno, "stat upperdir"); + + ret = write_permission_xattr (&lo, get_upper_layer (&lo)->fd, + lo.upperdir, + st.st_uid, st.st_gid, st.st_mode); + if (ret < 0) + error (EXIT_FAILURE, errno, "write xattr `%s` to upperdir", name); + } + } + } + } + lo.inodes = hash_initialize (2048, NULL, node_inode_hasher, node_inode_compare, inode_free); lo.root = load_dir (&lo, NULL, lo.layers, ".", ""); diff --git a/utils.c b/utils.c index 3d0e0aa..3fdb5fb 100644 --- a/utils.c +++ b/utils.c @@ -29,6 +29,7 @@ #include #include #include +#include #ifndef TEMP_FAILURE_RETRY #define TEMP_FAILURE_RETRY(expression) \ @@ -222,3 +223,70 @@ open_fd_or_get_path (struct ovl_layer *l, const char *path, char *out, int *fd, return *fd; } + +int +override_mode (struct ovl_layer *l, int fd, const char *abs_path, const char *path, struct stat *st) +{ + int ret; + uid_t uid; + gid_t gid; + mode_t mode; + char buf[64]; + cleanup_close int cleanup_fd = -1; + const char *xattr_name; + + if (l->has_stat_override == 0 && l->has_privileged_stat_override == 0) + return 0; + + xattr_name = l->has_privileged_stat_override ? XATTR_PRIVILEGED_OVERRIDE_STAT : XATTR_OVERRIDE_STAT; + + if (fd >= 0) + { + ret = fgetxattr (fd, xattr_name, buf, sizeof (buf) - 1); + if (ret < 0) + return ret; + } + else if (abs_path) + { + ret = lgetxattr (abs_path, xattr_name, buf, sizeof (buf) - 1); + if (ret < 0) + return ret; + } + else + { + char full_path[PATH_MAX]; + + full_path[0] = '\0'; + ret = open_fd_or_get_path (l, path, full_path, &cleanup_fd, O_RDONLY); + if (ret < 0) + return ret; + fd = cleanup_fd; + + if (fd >= 0) + ret = fgetxattr (fd, xattr_name, buf, sizeof (buf) - 1); + else + { + ret = lgetxattr (full_path, xattr_name, buf, sizeof (buf) - 1); + if (ret < 0 && errno == ENODATA) + return 0; + } + + if (ret < 0) + return ret; + } + + buf[ret] = '\0'; + + ret = sscanf (buf, "%d:%d:%o", &uid, &gid, &mode); + if (ret != 3) + { + errno = EINVAL; + return -1; + } + + st->st_uid = uid; + st->st_gid = gid; + st->st_mode = (st->st_mode & S_IFMT) | mode; + + return 0; +} diff --git a/utils.h b/utils.h index b447df9..99e911a 100644 --- a/utils.h +++ b/utils.h @@ -31,6 +31,9 @@ # include # include "fuse-overlayfs.h" +# define XATTR_OVERRIDE_STAT "user.fuseoverlayfs.override_stat" +# define XATTR_PRIVILEGED_OVERRIDE_STAT "security.fuseoverlayfs.override_stat" + void cleanup_freep (void *p); void cleanup_filep (FILE **f); void cleanup_closep (void *p); @@ -55,4 +58,6 @@ void statx_to_stat (struct statx *stx, struct stat *st); int safe_openat (int dirfd, const char *pathname, int flags, mode_t mode); +int override_mode (struct ovl_layer *l, int fd, const char *abs_path, const char *path, struct stat *st); + #endif