]>
Commit | Line | Data |
---|---|---|
9bc61ab1 DH |
1 | /* Provide a way to create a superblock configuration context within the kernel |
2 | * that allows a superblock to be set up prior to mounting. | |
3 | * | |
4 | * Copyright (C) 2017 Red Hat, Inc. All Rights Reserved. | |
5 | * Written by David Howells ([email protected]) | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or | |
8 | * modify it under the terms of the GNU General Public Licence | |
9 | * as published by the Free Software Foundation; either version | |
10 | * 2 of the Licence, or (at your option) any later version. | |
11 | */ | |
12 | ||
13 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
14 | #include <linux/fs_context.h> | |
3e1aeb00 | 15 | #include <linux/fs_parser.h> |
9bc61ab1 DH |
16 | #include <linux/fs.h> |
17 | #include <linux/mount.h> | |
18 | #include <linux/nsproxy.h> | |
19 | #include <linux/slab.h> | |
20 | #include <linux/magic.h> | |
21 | #include <linux/security.h> | |
22 | #include <linux/mnt_namespace.h> | |
23 | #include <linux/pid_namespace.h> | |
24 | #include <linux/user_namespace.h> | |
25 | #include <net/net_namespace.h> | |
26 | #include "mount.h" | |
27 | #include "internal.h" | |
28 | ||
3e1aeb00 DH |
29 | enum legacy_fs_param { |
30 | LEGACY_FS_UNSET_PARAMS, | |
31 | LEGACY_FS_MONOLITHIC_PARAMS, | |
32 | LEGACY_FS_INDIVIDUAL_PARAMS, | |
33 | }; | |
34 | ||
9bc61ab1 DH |
35 | struct legacy_fs_context { |
36 | char *legacy_data; /* Data page for legacy filesystems */ | |
37 | size_t data_size; | |
3e1aeb00 | 38 | enum legacy_fs_param param_type; |
9bc61ab1 DH |
39 | }; |
40 | ||
41 | static int legacy_init_fs_context(struct fs_context *fc); | |
42 | ||
3e1aeb00 DH |
43 | static const struct constant_table common_set_sb_flag[] = { |
44 | { "dirsync", SB_DIRSYNC }, | |
45 | { "lazytime", SB_LAZYTIME }, | |
46 | { "mand", SB_MANDLOCK }, | |
47 | { "posixacl", SB_POSIXACL }, | |
48 | { "ro", SB_RDONLY }, | |
49 | { "sync", SB_SYNCHRONOUS }, | |
50 | }; | |
51 | ||
52 | static const struct constant_table common_clear_sb_flag[] = { | |
53 | { "async", SB_SYNCHRONOUS }, | |
54 | { "nolazytime", SB_LAZYTIME }, | |
55 | { "nomand", SB_MANDLOCK }, | |
56 | { "rw", SB_RDONLY }, | |
57 | { "silent", SB_SILENT }, | |
58 | }; | |
59 | ||
60 | static const char *const forbidden_sb_flag[] = { | |
61 | "bind", | |
62 | "dev", | |
63 | "exec", | |
64 | "move", | |
65 | "noatime", | |
66 | "nodev", | |
67 | "nodiratime", | |
68 | "noexec", | |
69 | "norelatime", | |
70 | "nostrictatime", | |
71 | "nosuid", | |
72 | "private", | |
73 | "rec", | |
74 | "relatime", | |
75 | "remount", | |
76 | "shared", | |
77 | "slave", | |
78 | "strictatime", | |
79 | "suid", | |
80 | "unbindable", | |
81 | }; | |
82 | ||
83 | /* | |
84 | * Check for a common mount option that manipulates s_flags. | |
85 | */ | |
86 | static int vfs_parse_sb_flag(struct fs_context *fc, const char *key) | |
87 | { | |
88 | unsigned int token; | |
89 | unsigned int i; | |
90 | ||
91 | for (i = 0; i < ARRAY_SIZE(forbidden_sb_flag); i++) | |
92 | if (strcmp(key, forbidden_sb_flag[i]) == 0) | |
93 | return -EINVAL; | |
94 | ||
95 | token = lookup_constant(common_set_sb_flag, key, 0); | |
96 | if (token) { | |
97 | fc->sb_flags |= token; | |
98 | fc->sb_flags_mask |= token; | |
99 | return 0; | |
100 | } | |
101 | ||
102 | token = lookup_constant(common_clear_sb_flag, key, 0); | |
103 | if (token) { | |
104 | fc->sb_flags &= ~token; | |
105 | fc->sb_flags_mask |= token; | |
106 | return 0; | |
107 | } | |
108 | ||
109 | return -ENOPARAM; | |
110 | } | |
111 | ||
112 | /** | |
113 | * vfs_parse_fs_param - Add a single parameter to a superblock config | |
114 | * @fc: The filesystem context to modify | |
115 | * @param: The parameter | |
116 | * | |
117 | * A single mount option in string form is applied to the filesystem context | |
118 | * being set up. Certain standard options (for example "ro") are translated | |
119 | * into flag bits without going to the filesystem. The active security module | |
120 | * is allowed to observe and poach options. Any other options are passed over | |
121 | * to the filesystem to parse. | |
122 | * | |
123 | * This may be called multiple times for a context. | |
124 | * | |
125 | * Returns 0 on success and a negative error code on failure. In the event of | |
126 | * failure, supplementary error information may have been set. | |
127 | */ | |
128 | int vfs_parse_fs_param(struct fs_context *fc, struct fs_parameter *param) | |
129 | { | |
130 | int ret; | |
131 | ||
132 | if (!param->key) | |
133 | return invalf(fc, "Unnamed parameter\n"); | |
134 | ||
135 | ret = vfs_parse_sb_flag(fc, param->key); | |
136 | if (ret != -ENOPARAM) | |
137 | return ret; | |
138 | ||
139 | ret = security_fs_context_parse_param(fc, param); | |
140 | if (ret != -ENOPARAM) | |
141 | /* Param belongs to the LSM or is disallowed by the LSM; so | |
142 | * don't pass to the FS. | |
143 | */ | |
144 | return ret; | |
145 | ||
146 | if (fc->ops->parse_param) { | |
147 | ret = fc->ops->parse_param(fc, param); | |
148 | if (ret != -ENOPARAM) | |
149 | return ret; | |
150 | } | |
151 | ||
152 | /* If the filesystem doesn't take any arguments, give it the | |
153 | * default handling of source. | |
154 | */ | |
155 | if (strcmp(param->key, "source") == 0) { | |
156 | if (param->type != fs_value_is_string) | |
157 | return invalf(fc, "VFS: Non-string source"); | |
158 | if (fc->source) | |
159 | return invalf(fc, "VFS: Multiple sources"); | |
160 | fc->source = param->string; | |
161 | param->string = NULL; | |
162 | return 0; | |
163 | } | |
164 | ||
165 | return invalf(fc, "%s: Unknown parameter '%s'", | |
166 | fc->fs_type->name, param->key); | |
167 | } | |
168 | EXPORT_SYMBOL(vfs_parse_fs_param); | |
169 | ||
170 | /** | |
171 | * vfs_parse_fs_string - Convenience function to just parse a string. | |
172 | */ | |
173 | int vfs_parse_fs_string(struct fs_context *fc, const char *key, | |
174 | const char *value, size_t v_size) | |
175 | { | |
176 | int ret; | |
177 | ||
178 | struct fs_parameter param = { | |
179 | .key = key, | |
180 | .type = fs_value_is_string, | |
181 | .size = v_size, | |
182 | }; | |
183 | ||
184 | if (v_size > 0) { | |
185 | param.string = kmemdup_nul(value, v_size, GFP_KERNEL); | |
186 | if (!param.string) | |
187 | return -ENOMEM; | |
188 | } | |
189 | ||
190 | ret = vfs_parse_fs_param(fc, ¶m); | |
191 | kfree(param.string); | |
192 | return ret; | |
193 | } | |
194 | EXPORT_SYMBOL(vfs_parse_fs_string); | |
195 | ||
196 | /** | |
197 | * generic_parse_monolithic - Parse key[=val][,key[=val]]* mount data | |
198 | * @ctx: The superblock configuration to fill in. | |
199 | * @data: The data to parse | |
200 | * | |
201 | * Parse a blob of data that's in key[=val][,key[=val]]* form. This can be | |
202 | * called from the ->monolithic_mount_data() fs_context operation. | |
203 | * | |
204 | * Returns 0 on success or the error returned by the ->parse_option() fs_context | |
205 | * operation on failure. | |
206 | */ | |
207 | int generic_parse_monolithic(struct fs_context *fc, void *data) | |
208 | { | |
209 | char *options = data, *key; | |
210 | int ret = 0; | |
211 | ||
212 | if (!options) | |
213 | return 0; | |
214 | ||
215 | ret = security_sb_eat_lsm_opts(options, &fc->security); | |
216 | if (ret) | |
217 | return ret; | |
218 | ||
219 | while ((key = strsep(&options, ",")) != NULL) { | |
220 | if (*key) { | |
221 | size_t v_len = 0; | |
222 | char *value = strchr(key, '='); | |
223 | ||
224 | if (value) { | |
225 | if (value == key) | |
226 | continue; | |
227 | *value++ = 0; | |
228 | v_len = strlen(value); | |
229 | } | |
230 | ret = vfs_parse_fs_string(fc, key, value, v_len); | |
231 | if (ret < 0) | |
232 | break; | |
233 | } | |
234 | } | |
235 | ||
236 | return ret; | |
237 | } | |
238 | EXPORT_SYMBOL(generic_parse_monolithic); | |
239 | ||
9bc61ab1 DH |
240 | /** |
241 | * alloc_fs_context - Create a filesystem context. | |
242 | * @fs_type: The filesystem type. | |
243 | * @reference: The dentry from which this one derives (or NULL) | |
244 | * @sb_flags: Filesystem/superblock flags (SB_*) | |
245 | * @sb_flags_mask: Applicable members of @sb_flags | |
246 | * @purpose: The purpose that this configuration shall be used for. | |
247 | * | |
248 | * Open a filesystem and create a mount context. The mount context is | |
249 | * initialised with the supplied flags and, if a submount/automount from | |
250 | * another superblock (referred to by @reference) is supplied, may have | |
251 | * parameters such as namespaces copied across from that superblock. | |
252 | */ | |
253 | static struct fs_context *alloc_fs_context(struct file_system_type *fs_type, | |
254 | struct dentry *reference, | |
255 | unsigned int sb_flags, | |
256 | unsigned int sb_flags_mask, | |
257 | enum fs_context_purpose purpose) | |
258 | { | |
f3a09c92 | 259 | int (*init_fs_context)(struct fs_context *); |
9bc61ab1 DH |
260 | struct fs_context *fc; |
261 | int ret = -ENOMEM; | |
262 | ||
263 | fc = kzalloc(sizeof(struct fs_context), GFP_KERNEL); | |
264 | if (!fc) | |
265 | return ERR_PTR(-ENOMEM); | |
266 | ||
267 | fc->purpose = purpose; | |
268 | fc->sb_flags = sb_flags; | |
269 | fc->sb_flags_mask = sb_flags_mask; | |
270 | fc->fs_type = get_filesystem(fs_type); | |
271 | fc->cred = get_current_cred(); | |
272 | fc->net_ns = get_net(current->nsproxy->net_ns); | |
273 | ||
274 | switch (purpose) { | |
275 | case FS_CONTEXT_FOR_MOUNT: | |
276 | fc->user_ns = get_user_ns(fc->cred->user_ns); | |
277 | break; | |
e1a91586 AV |
278 | case FS_CONTEXT_FOR_SUBMOUNT: |
279 | fc->user_ns = get_user_ns(reference->d_sb->s_user_ns); | |
280 | break; | |
8d0347f6 DH |
281 | case FS_CONTEXT_FOR_RECONFIGURE: |
282 | /* We don't pin any namespaces as the superblock's | |
283 | * subscriptions cannot be changed at this point. | |
284 | */ | |
285 | atomic_inc(&reference->d_sb->s_active); | |
286 | fc->root = dget(reference); | |
287 | break; | |
9bc61ab1 DH |
288 | } |
289 | ||
f3a09c92 AV |
290 | /* TODO: Make all filesystems support this unconditionally */ |
291 | init_fs_context = fc->fs_type->init_fs_context; | |
292 | if (!init_fs_context) | |
293 | init_fs_context = legacy_init_fs_context; | |
294 | ||
295 | ret = init_fs_context(fc); | |
9bc61ab1 DH |
296 | if (ret < 0) |
297 | goto err_fc; | |
298 | fc->need_free = true; | |
299 | return fc; | |
300 | ||
301 | err_fc: | |
302 | put_fs_context(fc); | |
303 | return ERR_PTR(ret); | |
304 | } | |
305 | ||
306 | struct fs_context *fs_context_for_mount(struct file_system_type *fs_type, | |
307 | unsigned int sb_flags) | |
308 | { | |
309 | return alloc_fs_context(fs_type, NULL, sb_flags, 0, | |
310 | FS_CONTEXT_FOR_MOUNT); | |
311 | } | |
312 | EXPORT_SYMBOL(fs_context_for_mount); | |
313 | ||
8d0347f6 DH |
314 | struct fs_context *fs_context_for_reconfigure(struct dentry *dentry, |
315 | unsigned int sb_flags, | |
316 | unsigned int sb_flags_mask) | |
317 | { | |
318 | return alloc_fs_context(dentry->d_sb->s_type, dentry, sb_flags, | |
319 | sb_flags_mask, FS_CONTEXT_FOR_RECONFIGURE); | |
320 | } | |
321 | EXPORT_SYMBOL(fs_context_for_reconfigure); | |
322 | ||
e1a91586 AV |
323 | struct fs_context *fs_context_for_submount(struct file_system_type *type, |
324 | struct dentry *reference) | |
325 | { | |
326 | return alloc_fs_context(type, reference, 0, 0, FS_CONTEXT_FOR_SUBMOUNT); | |
327 | } | |
328 | EXPORT_SYMBOL(fs_context_for_submount); | |
329 | ||
c9ce29ed AV |
330 | void fc_drop_locked(struct fs_context *fc) |
331 | { | |
332 | struct super_block *sb = fc->root->d_sb; | |
333 | dput(fc->root); | |
334 | fc->root = NULL; | |
335 | deactivate_locked_super(sb); | |
336 | } | |
337 | ||
9bc61ab1 | 338 | static void legacy_fs_context_free(struct fs_context *fc); |
8d0347f6 | 339 | |
9bc61ab1 DH |
340 | /** |
341 | * put_fs_context - Dispose of a superblock configuration context. | |
342 | * @fc: The context to dispose of. | |
343 | */ | |
344 | void put_fs_context(struct fs_context *fc) | |
345 | { | |
346 | struct super_block *sb; | |
347 | ||
348 | if (fc->root) { | |
349 | sb = fc->root->d_sb; | |
350 | dput(fc->root); | |
351 | fc->root = NULL; | |
352 | deactivate_super(sb); | |
353 | } | |
354 | ||
f3a09c92 AV |
355 | if (fc->need_free && fc->ops && fc->ops->free) |
356 | fc->ops->free(fc); | |
9bc61ab1 DH |
357 | |
358 | security_free_mnt_opts(&fc->security); | |
8d0347f6 | 359 | put_net(fc->net_ns); |
9bc61ab1 DH |
360 | put_user_ns(fc->user_ns); |
361 | put_cred(fc->cred); | |
362 | kfree(fc->subtype); | |
363 | put_filesystem(fc->fs_type); | |
364 | kfree(fc->source); | |
365 | kfree(fc); | |
366 | } | |
367 | EXPORT_SYMBOL(put_fs_context); | |
368 | ||
369 | /* | |
370 | * Free the config for a filesystem that doesn't support fs_context. | |
371 | */ | |
372 | static void legacy_fs_context_free(struct fs_context *fc) | |
373 | { | |
3e1aeb00 DH |
374 | struct legacy_fs_context *ctx = fc->fs_private; |
375 | ||
376 | if (ctx) { | |
377 | if (ctx->param_type == LEGACY_FS_INDIVIDUAL_PARAMS) | |
378 | kfree(ctx->legacy_data); | |
379 | kfree(ctx); | |
380 | } | |
381 | } | |
382 | ||
383 | /* | |
384 | * Add a parameter to a legacy config. We build up a comma-separated list of | |
385 | * options. | |
386 | */ | |
387 | static int legacy_parse_param(struct fs_context *fc, struct fs_parameter *param) | |
388 | { | |
389 | struct legacy_fs_context *ctx = fc->fs_private; | |
390 | unsigned int size = ctx->data_size; | |
391 | size_t len = 0; | |
392 | ||
393 | if (strcmp(param->key, "source") == 0) { | |
394 | if (param->type != fs_value_is_string) | |
395 | return invalf(fc, "VFS: Legacy: Non-string source"); | |
396 | if (fc->source) | |
397 | return invalf(fc, "VFS: Legacy: Multiple sources"); | |
398 | fc->source = param->string; | |
399 | param->string = NULL; | |
400 | return 0; | |
401 | } | |
402 | ||
403 | if ((fc->fs_type->fs_flags & FS_HAS_SUBTYPE) && | |
404 | strcmp(param->key, "subtype") == 0) { | |
405 | if (param->type != fs_value_is_string) | |
406 | return invalf(fc, "VFS: Legacy: Non-string subtype"); | |
407 | if (fc->subtype) | |
408 | return invalf(fc, "VFS: Legacy: Multiple subtype"); | |
409 | fc->subtype = param->string; | |
410 | param->string = NULL; | |
411 | return 0; | |
412 | } | |
413 | ||
414 | if (ctx->param_type == LEGACY_FS_MONOLITHIC_PARAMS) | |
415 | return invalf(fc, "VFS: Legacy: Can't mix monolithic and individual options"); | |
416 | ||
417 | switch (param->type) { | |
418 | case fs_value_is_string: | |
419 | len = 1 + param->size; | |
420 | /* Fall through */ | |
421 | case fs_value_is_flag: | |
422 | len += strlen(param->key); | |
423 | break; | |
424 | default: | |
425 | return invalf(fc, "VFS: Legacy: Parameter type for '%s' not supported", | |
426 | param->key); | |
427 | } | |
428 | ||
429 | if (len > PAGE_SIZE - 2 - size) | |
430 | return invalf(fc, "VFS: Legacy: Cumulative options too large"); | |
431 | if (strchr(param->key, ',') || | |
432 | (param->type == fs_value_is_string && | |
433 | memchr(param->string, ',', param->size))) | |
434 | return invalf(fc, "VFS: Legacy: Option '%s' contained comma", | |
435 | param->key); | |
436 | if (!ctx->legacy_data) { | |
437 | ctx->legacy_data = kmalloc(PAGE_SIZE, GFP_KERNEL); | |
438 | if (!ctx->legacy_data) | |
439 | return -ENOMEM; | |
440 | } | |
441 | ||
442 | ctx->legacy_data[size++] = ','; | |
443 | len = strlen(param->key); | |
444 | memcpy(ctx->legacy_data + size, param->key, len); | |
445 | size += len; | |
446 | if (param->type == fs_value_is_string) { | |
447 | ctx->legacy_data[size++] = '='; | |
448 | memcpy(ctx->legacy_data + size, param->string, param->size); | |
449 | size += param->size; | |
450 | } | |
451 | ctx->legacy_data[size] = '\0'; | |
452 | ctx->data_size = size; | |
453 | ctx->param_type = LEGACY_FS_INDIVIDUAL_PARAMS; | |
454 | return 0; | |
9bc61ab1 DH |
455 | } |
456 | ||
457 | /* | |
458 | * Add monolithic mount data. | |
459 | */ | |
460 | static int legacy_parse_monolithic(struct fs_context *fc, void *data) | |
461 | { | |
462 | struct legacy_fs_context *ctx = fc->fs_private; | |
3e1aeb00 DH |
463 | |
464 | if (ctx->param_type != LEGACY_FS_UNSET_PARAMS) { | |
465 | pr_warn("VFS: Can't mix monolithic and individual options\n"); | |
466 | return -EINVAL; | |
467 | } | |
468 | ||
9bc61ab1 | 469 | ctx->legacy_data = data; |
3e1aeb00 | 470 | ctx->param_type = LEGACY_FS_MONOLITHIC_PARAMS; |
9bc61ab1 DH |
471 | if (!ctx->legacy_data) |
472 | return 0; | |
3e1aeb00 | 473 | |
9bc61ab1 DH |
474 | if (fc->fs_type->fs_flags & FS_BINARY_MOUNTDATA) |
475 | return 0; | |
476 | return security_sb_eat_lsm_opts(ctx->legacy_data, &fc->security); | |
477 | } | |
478 | ||
479 | /* | |
480 | * Get a mountable root with the legacy mount command. | |
481 | */ | |
f3a09c92 | 482 | static int legacy_get_tree(struct fs_context *fc) |
9bc61ab1 DH |
483 | { |
484 | struct legacy_fs_context *ctx = fc->fs_private; | |
485 | struct super_block *sb; | |
486 | struct dentry *root; | |
487 | ||
488 | root = fc->fs_type->mount(fc->fs_type, fc->sb_flags, | |
489 | fc->source, ctx->legacy_data); | |
490 | if (IS_ERR(root)) | |
491 | return PTR_ERR(root); | |
492 | ||
493 | sb = root->d_sb; | |
494 | BUG_ON(!sb); | |
495 | ||
496 | fc->root = root; | |
497 | return 0; | |
498 | } | |
499 | ||
8d0347f6 DH |
500 | /* |
501 | * Handle remount. | |
502 | */ | |
f3a09c92 | 503 | static int legacy_reconfigure(struct fs_context *fc) |
8d0347f6 DH |
504 | { |
505 | struct legacy_fs_context *ctx = fc->fs_private; | |
506 | struct super_block *sb = fc->root->d_sb; | |
507 | ||
508 | if (!sb->s_op->remount_fs) | |
509 | return 0; | |
510 | ||
511 | return sb->s_op->remount_fs(sb, &fc->sb_flags, | |
512 | ctx ? ctx->legacy_data : NULL); | |
513 | } | |
514 | ||
f3a09c92 AV |
515 | const struct fs_context_operations legacy_fs_context_ops = { |
516 | .free = legacy_fs_context_free, | |
3e1aeb00 | 517 | .parse_param = legacy_parse_param, |
f3a09c92 AV |
518 | .parse_monolithic = legacy_parse_monolithic, |
519 | .get_tree = legacy_get_tree, | |
520 | .reconfigure = legacy_reconfigure, | |
521 | }; | |
522 | ||
9bc61ab1 DH |
523 | /* |
524 | * Initialise a legacy context for a filesystem that doesn't support | |
525 | * fs_context. | |
526 | */ | |
527 | static int legacy_init_fs_context(struct fs_context *fc) | |
528 | { | |
529 | fc->fs_private = kzalloc(sizeof(struct legacy_fs_context), GFP_KERNEL); | |
530 | if (!fc->fs_private) | |
531 | return -ENOMEM; | |
f3a09c92 | 532 | fc->ops = &legacy_fs_context_ops; |
9bc61ab1 DH |
533 | return 0; |
534 | } | |
535 | ||
536 | int parse_monolithic_mount_data(struct fs_context *fc, void *data) | |
537 | { | |
f3a09c92 | 538 | int (*monolithic_mount_data)(struct fs_context *, void *); |
3e1aeb00 | 539 | |
f3a09c92 | 540 | monolithic_mount_data = fc->ops->parse_monolithic; |
3e1aeb00 DH |
541 | if (!monolithic_mount_data) |
542 | monolithic_mount_data = generic_parse_monolithic; | |
543 | ||
f3a09c92 | 544 | return monolithic_mount_data(fc, data); |
9bc61ab1 | 545 | } |