Commit | Line | Data |
---|---|---|
b4d0d230 | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
24dcb3d9 DH |
2 | /* Filesystem access-by-fd. |
3 | * | |
4 | * Copyright (C) 2017 Red Hat, Inc. All Rights Reserved. | |
5 | * Written by David Howells (dhowells@redhat.com) | |
24dcb3d9 DH |
6 | */ |
7 | ||
8 | #include <linux/fs_context.h> | |
ecdab150 | 9 | #include <linux/fs_parser.h> |
24dcb3d9 DH |
10 | #include <linux/slab.h> |
11 | #include <linux/uaccess.h> | |
12 | #include <linux/syscalls.h> | |
13 | #include <linux/security.h> | |
14 | #include <linux/anon_inodes.h> | |
15 | #include <linux/namei.h> | |
16 | #include <linux/file.h> | |
17 | #include <uapi/linux/mount.h> | |
ecdab150 | 18 | #include "internal.h" |
24dcb3d9 DH |
19 | #include "mount.h" |
20 | ||
007ec26c DH |
21 | /* |
22 | * Allow the user to read back any error, warning or informational messages. | |
23 | */ | |
24 | static ssize_t fscontext_read(struct file *file, | |
25 | char __user *_buf, size_t len, loff_t *pos) | |
26 | { | |
27 | struct fs_context *fc = file->private_data; | |
cc3c0b53 | 28 | struct fc_log *log = fc->log.log; |
007ec26c DH |
29 | unsigned int logsize = ARRAY_SIZE(log->buffer); |
30 | ssize_t ret; | |
31 | char *p; | |
32 | bool need_free; | |
33 | int index, n; | |
34 | ||
35 | ret = mutex_lock_interruptible(&fc->uapi_mutex); | |
36 | if (ret < 0) | |
37 | return ret; | |
38 | ||
39 | if (log->head == log->tail) { | |
40 | mutex_unlock(&fc->uapi_mutex); | |
41 | return -ENODATA; | |
42 | } | |
43 | ||
44 | index = log->tail & (logsize - 1); | |
45 | p = log->buffer[index]; | |
46 | need_free = log->need_free & (1 << index); | |
47 | log->buffer[index] = NULL; | |
48 | log->need_free &= ~(1 << index); | |
49 | log->tail++; | |
50 | mutex_unlock(&fc->uapi_mutex); | |
51 | ||
52 | ret = -EMSGSIZE; | |
53 | n = strlen(p); | |
54 | if (n > len) | |
55 | goto err_free; | |
56 | ret = -EFAULT; | |
57 | if (copy_to_user(_buf, p, n) != 0) | |
58 | goto err_free; | |
59 | ret = n; | |
60 | ||
61 | err_free: | |
62 | if (need_free) | |
63 | kfree(p); | |
64 | return ret; | |
65 | } | |
66 | ||
24dcb3d9 DH |
67 | static int fscontext_release(struct inode *inode, struct file *file) |
68 | { | |
69 | struct fs_context *fc = file->private_data; | |
70 | ||
71 | if (fc) { | |
72 | file->private_data = NULL; | |
73 | put_fs_context(fc); | |
74 | } | |
75 | return 0; | |
76 | } | |
77 | ||
78 | const struct file_operations fscontext_fops = { | |
007ec26c | 79 | .read = fscontext_read, |
24dcb3d9 | 80 | .release = fscontext_release, |
24dcb3d9 DH |
81 | }; |
82 | ||
83 | /* | |
84 | * Attach a filesystem context to a file and an fd. | |
85 | */ | |
86 | static int fscontext_create_fd(struct fs_context *fc, unsigned int o_flags) | |
87 | { | |
88 | int fd; | |
89 | ||
1cdc415f | 90 | fd = anon_inode_getfd("[fscontext]", &fscontext_fops, fc, |
24dcb3d9 DH |
91 | O_RDWR | o_flags); |
92 | if (fd < 0) | |
93 | put_fs_context(fc); | |
94 | return fd; | |
95 | } | |
96 | ||
007ec26c DH |
97 | static int fscontext_alloc_log(struct fs_context *fc) |
98 | { | |
cc3c0b53 AV |
99 | fc->log.log = kzalloc(sizeof(*fc->log.log), GFP_KERNEL); |
100 | if (!fc->log.log) | |
007ec26c | 101 | return -ENOMEM; |
cc3c0b53 AV |
102 | refcount_set(&fc->log.log->usage, 1); |
103 | fc->log.log->owner = fc->fs_type->owner; | |
007ec26c DH |
104 | return 0; |
105 | } | |
106 | ||
24dcb3d9 DH |
107 | /* |
108 | * Open a filesystem by name so that it can be configured for mounting. | |
109 | * | |
110 | * We are allowed to specify a container in which the filesystem will be | |
111 | * opened, thereby indicating which namespaces will be used (notably, which | |
112 | * network namespace will be used for network filesystems). | |
113 | */ | |
114 | SYSCALL_DEFINE2(fsopen, const char __user *, _fs_name, unsigned int, flags) | |
115 | { | |
116 | struct file_system_type *fs_type; | |
117 | struct fs_context *fc; | |
118 | const char *fs_name; | |
007ec26c | 119 | int ret; |
24dcb3d9 | 120 | |
a5f85d78 | 121 | if (!may_mount()) |
24dcb3d9 DH |
122 | return -EPERM; |
123 | ||
124 | if (flags & ~FSOPEN_CLOEXEC) | |
125 | return -EINVAL; | |
126 | ||
127 | fs_name = strndup_user(_fs_name, PAGE_SIZE); | |
128 | if (IS_ERR(fs_name)) | |
129 | return PTR_ERR(fs_name); | |
130 | ||
131 | fs_type = get_fs_type(fs_name); | |
132 | kfree(fs_name); | |
133 | if (!fs_type) | |
134 | return -ENODEV; | |
135 | ||
136 | fc = fs_context_for_mount(fs_type, 0); | |
137 | put_filesystem(fs_type); | |
138 | if (IS_ERR(fc)) | |
139 | return PTR_ERR(fc); | |
140 | ||
141 | fc->phase = FS_CONTEXT_CREATE_PARAMS; | |
007ec26c DH |
142 | |
143 | ret = fscontext_alloc_log(fc); | |
144 | if (ret < 0) | |
145 | goto err_fc; | |
146 | ||
24dcb3d9 | 147 | return fscontext_create_fd(fc, flags & FSOPEN_CLOEXEC ? O_CLOEXEC : 0); |
007ec26c DH |
148 | |
149 | err_fc: | |
150 | put_fs_context(fc); | |
151 | return ret; | |
24dcb3d9 | 152 | } |
ecdab150 | 153 | |
cf3cba4a DH |
154 | /* |
155 | * Pick a superblock into a context for reconfiguration. | |
156 | */ | |
157 | SYSCALL_DEFINE3(fspick, int, dfd, const char __user *, path, unsigned int, flags) | |
158 | { | |
159 | struct fs_context *fc; | |
160 | struct path target; | |
161 | unsigned int lookup_flags; | |
162 | int ret; | |
163 | ||
a5f85d78 | 164 | if (!may_mount()) |
cf3cba4a DH |
165 | return -EPERM; |
166 | ||
167 | if ((flags & ~(FSPICK_CLOEXEC | | |
168 | FSPICK_SYMLINK_NOFOLLOW | | |
169 | FSPICK_NO_AUTOMOUNT | | |
170 | FSPICK_EMPTY_PATH)) != 0) | |
171 | return -EINVAL; | |
172 | ||
173 | lookup_flags = LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT; | |
174 | if (flags & FSPICK_SYMLINK_NOFOLLOW) | |
175 | lookup_flags &= ~LOOKUP_FOLLOW; | |
176 | if (flags & FSPICK_NO_AUTOMOUNT) | |
177 | lookup_flags &= ~LOOKUP_AUTOMOUNT; | |
178 | if (flags & FSPICK_EMPTY_PATH) | |
179 | lookup_flags |= LOOKUP_EMPTY; | |
180 | ret = user_path_at(dfd, path, lookup_flags, &target); | |
181 | if (ret < 0) | |
182 | goto err; | |
183 | ||
184 | ret = -EINVAL; | |
185 | if (target.mnt->mnt_root != target.dentry) | |
186 | goto err_path; | |
187 | ||
188 | fc = fs_context_for_reconfigure(target.dentry, 0, 0); | |
189 | if (IS_ERR(fc)) { | |
190 | ret = PTR_ERR(fc); | |
191 | goto err_path; | |
192 | } | |
193 | ||
194 | fc->phase = FS_CONTEXT_RECONF_PARAMS; | |
195 | ||
196 | ret = fscontext_alloc_log(fc); | |
197 | if (ret < 0) | |
198 | goto err_fc; | |
199 | ||
200 | path_put(&target); | |
201 | return fscontext_create_fd(fc, flags & FSPICK_CLOEXEC ? O_CLOEXEC : 0); | |
202 | ||
203 | err_fc: | |
204 | put_fs_context(fc); | |
205 | err_path: | |
206 | path_put(&target); | |
207 | err: | |
208 | return ret; | |
209 | } | |
210 | ||
22ed7ecd | 211 | static int vfs_cmd_create(struct fs_context *fc, bool exclusive) |
dae8b08d CB |
212 | { |
213 | struct super_block *sb; | |
214 | int ret; | |
215 | ||
216 | if (fc->phase != FS_CONTEXT_CREATE_PARAMS) | |
217 | return -EBUSY; | |
218 | ||
219 | if (!mount_capable(fc)) | |
220 | return -EPERM; | |
221 | ||
222 | fc->phase = FS_CONTEXT_CREATING; | |
22ed7ecd | 223 | fc->exclusive = exclusive; |
dae8b08d CB |
224 | |
225 | ret = vfs_get_tree(fc); | |
226 | if (ret) { | |
227 | fc->phase = FS_CONTEXT_FAILED; | |
228 | return ret; | |
229 | } | |
230 | ||
231 | sb = fc->root->d_sb; | |
232 | ret = security_sb_kern_mount(sb); | |
233 | if (unlikely(ret)) { | |
234 | fc_drop_locked(fc); | |
235 | fc->phase = FS_CONTEXT_FAILED; | |
236 | return ret; | |
237 | } | |
238 | ||
239 | /* vfs_get_tree() callchains will have grabbed @s_umount */ | |
240 | up_write(&sb->s_umount); | |
241 | fc->phase = FS_CONTEXT_AWAITING_MOUNT; | |
242 | return 0; | |
243 | } | |
244 | ||
11a51d8c CB |
245 | static int vfs_cmd_reconfigure(struct fs_context *fc) |
246 | { | |
247 | struct super_block *sb; | |
248 | int ret; | |
249 | ||
250 | if (fc->phase != FS_CONTEXT_RECONF_PARAMS) | |
251 | return -EBUSY; | |
252 | ||
253 | fc->phase = FS_CONTEXT_RECONFIGURING; | |
254 | ||
255 | sb = fc->root->d_sb; | |
256 | if (!ns_capable(sb->s_user_ns, CAP_SYS_ADMIN)) { | |
257 | fc->phase = FS_CONTEXT_FAILED; | |
258 | return -EPERM; | |
259 | } | |
260 | ||
261 | down_write(&sb->s_umount); | |
262 | ret = reconfigure_super(fc); | |
263 | up_write(&sb->s_umount); | |
264 | if (ret) { | |
265 | fc->phase = FS_CONTEXT_FAILED; | |
266 | return ret; | |
267 | } | |
268 | ||
269 | vfs_clean_context(fc); | |
270 | return 0; | |
271 | } | |
272 | ||
ecdab150 DH |
273 | /* |
274 | * Check the state and apply the configuration. Note that this function is | |
275 | * allowed to 'steal' the value by setting param->xxx to NULL before returning. | |
276 | */ | |
277 | static int vfs_fsconfig_locked(struct fs_context *fc, int cmd, | |
278 | struct fs_parameter *param) | |
279 | { | |
ecdab150 DH |
280 | int ret; |
281 | ||
282 | ret = finish_clean_context(fc); | |
283 | if (ret) | |
284 | return ret; | |
285 | switch (cmd) { | |
286 | case FSCONFIG_CMD_CREATE: | |
22ed7ecd CB |
287 | return vfs_cmd_create(fc, false); |
288 | case FSCONFIG_CMD_CREATE_EXCL: | |
289 | return vfs_cmd_create(fc, true); | |
ecdab150 | 290 | case FSCONFIG_CMD_RECONFIGURE: |
11a51d8c | 291 | return vfs_cmd_reconfigure(fc); |
ecdab150 DH |
292 | default: |
293 | if (fc->phase != FS_CONTEXT_CREATE_PARAMS && | |
294 | fc->phase != FS_CONTEXT_RECONF_PARAMS) | |
295 | return -EBUSY; | |
296 | ||
297 | return vfs_parse_fs_param(fc, param); | |
298 | } | |
ecdab150 DH |
299 | } |
300 | ||
301 | /** | |
302 | * sys_fsconfig - Set parameters and trigger actions on a context | |
303 | * @fd: The filesystem context to act upon | |
304 | * @cmd: The action to take | |
305 | * @_key: Where appropriate, the parameter key to set | |
306 | * @_value: Where appropriate, the parameter value to set | |
307 | * @aux: Additional information for the value | |
308 | * | |
309 | * This system call is used to set parameters on a context, including | |
310 | * superblock settings, data source and security labelling. | |
311 | * | |
312 | * Actions include triggering the creation of a superblock and the | |
313 | * reconfiguration of the superblock attached to the specified context. | |
314 | * | |
315 | * When setting a parameter, @cmd indicates the type of value being proposed | |
316 | * and @_key indicates the parameter to be altered. | |
317 | * | |
318 | * @_value and @aux are used to specify the value, should a value be required: | |
319 | * | |
320 | * (*) fsconfig_set_flag: No value is specified. The parameter must be boolean | |
321 | * in nature. The key may be prefixed with "no" to invert the | |
322 | * setting. @_value must be NULL and @aux must be 0. | |
323 | * | |
324 | * (*) fsconfig_set_string: A string value is specified. The parameter can be | |
325 | * expecting boolean, integer, string or take a path. A conversion to an | |
326 | * appropriate type will be attempted (which may include looking up as a | |
327 | * path). @_value points to a NUL-terminated string and @aux must be 0. | |
328 | * | |
329 | * (*) fsconfig_set_binary: A binary blob is specified. @_value points to the | |
330 | * blob and @aux indicates its size. The parameter must be expecting a | |
331 | * blob. | |
332 | * | |
333 | * (*) fsconfig_set_path: A non-empty path is specified. The parameter must be | |
334 | * expecting a path object. @_value points to a NUL-terminated string that | |
335 | * is the path and @aux is a file descriptor at which to start a relative | |
336 | * lookup or AT_FDCWD. | |
337 | * | |
338 | * (*) fsconfig_set_path_empty: As fsconfig_set_path, but with AT_EMPTY_PATH | |
339 | * implied. | |
340 | * | |
341 | * (*) fsconfig_set_fd: An open file descriptor is specified. @_value must be | |
342 | * NULL and @aux indicates the file descriptor. | |
343 | */ | |
344 | SYSCALL_DEFINE5(fsconfig, | |
345 | int, fd, | |
346 | unsigned int, cmd, | |
347 | const char __user *, _key, | |
348 | const void __user *, _value, | |
349 | int, aux) | |
350 | { | |
351 | struct fs_context *fc; | |
ecdab150 | 352 | int ret; |
aa1918f9 | 353 | int lookup_flags = 0; |
ecdab150 DH |
354 | |
355 | struct fs_parameter param = { | |
356 | .type = fs_value_is_undefined, | |
357 | }; | |
358 | ||
359 | if (fd < 0) | |
360 | return -EINVAL; | |
361 | ||
362 | switch (cmd) { | |
363 | case FSCONFIG_SET_FLAG: | |
364 | if (!_key || _value || aux) | |
365 | return -EINVAL; | |
366 | break; | |
367 | case FSCONFIG_SET_STRING: | |
368 | if (!_key || !_value || aux) | |
369 | return -EINVAL; | |
370 | break; | |
371 | case FSCONFIG_SET_BINARY: | |
372 | if (!_key || !_value || aux <= 0 || aux > 1024 * 1024) | |
373 | return -EINVAL; | |
374 | break; | |
375 | case FSCONFIG_SET_PATH: | |
376 | case FSCONFIG_SET_PATH_EMPTY: | |
377 | if (!_key || !_value || (aux != AT_FDCWD && aux < 0)) | |
378 | return -EINVAL; | |
379 | break; | |
380 | case FSCONFIG_SET_FD: | |
381 | if (!_key || _value || aux < 0) | |
382 | return -EINVAL; | |
383 | break; | |
384 | case FSCONFIG_CMD_CREATE: | |
22ed7ecd | 385 | case FSCONFIG_CMD_CREATE_EXCL: |
ecdab150 DH |
386 | case FSCONFIG_CMD_RECONFIGURE: |
387 | if (_key || _value || aux) | |
388 | return -EINVAL; | |
389 | break; | |
390 | default: | |
391 | return -EOPNOTSUPP; | |
392 | } | |
393 | ||
8152f820 AV |
394 | CLASS(fd, f)(fd); |
395 | if (fd_empty(f)) | |
ecdab150 | 396 | return -EBADF; |
1da91ea8 | 397 | if (fd_file(f)->f_op != &fscontext_fops) |
8152f820 | 398 | return -EINVAL; |
ecdab150 | 399 | |
1da91ea8 | 400 | fc = fd_file(f)->private_data; |
ecdab150 DH |
401 | if (fc->ops == &legacy_fs_context_ops) { |
402 | switch (cmd) { | |
403 | case FSCONFIG_SET_BINARY: | |
404 | case FSCONFIG_SET_PATH: | |
405 | case FSCONFIG_SET_PATH_EMPTY: | |
406 | case FSCONFIG_SET_FD: | |
ef44c8ab | 407 | case FSCONFIG_CMD_CREATE_EXCL: |
8152f820 | 408 | return -EOPNOTSUPP; |
ecdab150 DH |
409 | } |
410 | } | |
411 | ||
412 | if (_key) { | |
413 | param.key = strndup_user(_key, 256); | |
8152f820 AV |
414 | if (IS_ERR(param.key)) |
415 | return PTR_ERR(param.key); | |
ecdab150 DH |
416 | } |
417 | ||
418 | switch (cmd) { | |
419 | case FSCONFIG_SET_FLAG: | |
420 | param.type = fs_value_is_flag; | |
421 | break; | |
422 | case FSCONFIG_SET_STRING: | |
423 | param.type = fs_value_is_string; | |
424 | param.string = strndup_user(_value, 256); | |
425 | if (IS_ERR(param.string)) { | |
426 | ret = PTR_ERR(param.string); | |
427 | goto out_key; | |
428 | } | |
429 | param.size = strlen(param.string); | |
430 | break; | |
431 | case FSCONFIG_SET_BINARY: | |
432 | param.type = fs_value_is_blob; | |
433 | param.size = aux; | |
434 | param.blob = memdup_user_nul(_value, aux); | |
435 | if (IS_ERR(param.blob)) { | |
436 | ret = PTR_ERR(param.blob); | |
437 | goto out_key; | |
438 | } | |
439 | break; | |
aa1918f9 AV |
440 | case FSCONFIG_SET_PATH_EMPTY: |
441 | lookup_flags = LOOKUP_EMPTY; | |
df561f66 | 442 | fallthrough; |
ecdab150 DH |
443 | case FSCONFIG_SET_PATH: |
444 | param.type = fs_value_is_filename; | |
dff60734 | 445 | param.name = getname_flags(_value, lookup_flags); |
ecdab150 DH |
446 | if (IS_ERR(param.name)) { |
447 | ret = PTR_ERR(param.name); | |
448 | goto out_key; | |
449 | } | |
450 | param.dirfd = aux; | |
451 | param.size = strlen(param.name->name); | |
452 | break; | |
453 | case FSCONFIG_SET_FD: | |
454 | param.type = fs_value_is_file; | |
455 | ret = -EBADF; | |
0ff053b9 | 456 | param.file = fget_raw(aux); |
ecdab150 DH |
457 | if (!param.file) |
458 | goto out_key; | |
9cf16b38 | 459 | param.dirfd = aux; |
ecdab150 DH |
460 | break; |
461 | default: | |
462 | break; | |
463 | } | |
464 | ||
465 | ret = mutex_lock_interruptible(&fc->uapi_mutex); | |
466 | if (ret == 0) { | |
467 | ret = vfs_fsconfig_locked(fc, cmd, ¶m); | |
468 | mutex_unlock(&fc->uapi_mutex); | |
469 | } | |
470 | ||
471 | /* Clean up the our record of any value that we obtained from | |
472 | * userspace. Note that the value may have been stolen by the LSM or | |
473 | * filesystem, in which case the value pointer will have been cleared. | |
474 | */ | |
475 | switch (cmd) { | |
476 | case FSCONFIG_SET_STRING: | |
477 | case FSCONFIG_SET_BINARY: | |
478 | kfree(param.string); | |
479 | break; | |
480 | case FSCONFIG_SET_PATH: | |
481 | case FSCONFIG_SET_PATH_EMPTY: | |
482 | if (param.name) | |
483 | putname(param.name); | |
484 | break; | |
485 | case FSCONFIG_SET_FD: | |
486 | if (param.file) | |
487 | fput(param.file); | |
488 | break; | |
489 | default: | |
490 | break; | |
491 | } | |
492 | out_key: | |
493 | kfree(param.key); | |
ecdab150 DH |
494 | return ret; |
495 | } |