xref: /linux-6.15/fs/fs_parser.c (revision 96cafb9c)
1b4d0d230SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
231d921c7SDavid Howells /* Filesystem parameter parser.
331d921c7SDavid Howells  *
431d921c7SDavid Howells  * Copyright (C) 2018 Red Hat, Inc. All Rights Reserved.
531d921c7SDavid Howells  * Written by David Howells ([email protected])
631d921c7SDavid Howells  */
731d921c7SDavid Howells 
831d921c7SDavid Howells #include <linux/export.h>
931d921c7SDavid Howells #include <linux/fs_context.h>
1031d921c7SDavid Howells #include <linux/fs_parser.h>
1131d921c7SDavid Howells #include <linux/slab.h>
1231d921c7SDavid Howells #include <linux/security.h>
1331d921c7SDavid Howells #include <linux/namei.h>
1431d921c7SDavid Howells #include "internal.h"
1531d921c7SDavid Howells 
1631d921c7SDavid Howells static const struct constant_table bool_names[] = {
1731d921c7SDavid Howells 	{ "0",		false },
1831d921c7SDavid Howells 	{ "1",		true },
1931d921c7SDavid Howells 	{ "false",	false },
2031d921c7SDavid Howells 	{ "no",		false },
2131d921c7SDavid Howells 	{ "true",	true },
2231d921c7SDavid Howells 	{ "yes",	true },
2334264ae3SAl Viro 	{ },
2431d921c7SDavid Howells };
2531d921c7SDavid Howells 
2634264ae3SAl Viro static const struct constant_table *
2734264ae3SAl Viro __lookup_constant(const struct constant_table *tbl, const char *name)
2834264ae3SAl Viro {
2934264ae3SAl Viro 	for ( ; tbl->name; tbl++)
3034264ae3SAl Viro 		if (strcmp(name, tbl->name) == 0)
3134264ae3SAl Viro 			return tbl;
3234264ae3SAl Viro 	return NULL;
3334264ae3SAl Viro }
3434264ae3SAl Viro 
3531d921c7SDavid Howells /**
3631d921c7SDavid Howells  * lookup_constant - Look up a constant by name in an ordered table
3731d921c7SDavid Howells  * @tbl: The table of constants to search.
3831d921c7SDavid Howells  * @name: The name to look up.
3931d921c7SDavid Howells  * @not_found: The value to return if the name is not found.
4031d921c7SDavid Howells  */
4134264ae3SAl Viro int lookup_constant(const struct constant_table *tbl, const char *name, int not_found)
4231d921c7SDavid Howells {
4334264ae3SAl Viro 	const struct constant_table *p = __lookup_constant(tbl, name);
4431d921c7SDavid Howells 
4534264ae3SAl Viro 	return p ? p->value : not_found;
4631d921c7SDavid Howells }
4734264ae3SAl Viro EXPORT_SYMBOL(lookup_constant);
4831d921c7SDavid Howells 
4931d921c7SDavid Howells static const struct fs_parameter_spec *fs_lookup_key(
5031d921c7SDavid Howells 	const struct fs_parameter_description *desc,
5131d921c7SDavid Howells 	const char *name)
5231d921c7SDavid Howells {
5331d921c7SDavid Howells 	const struct fs_parameter_spec *p;
5431d921c7SDavid Howells 
5531d921c7SDavid Howells 	if (!desc->specs)
5631d921c7SDavid Howells 		return NULL;
5731d921c7SDavid Howells 
5831d921c7SDavid Howells 	for (p = desc->specs; p->name; p++)
5931d921c7SDavid Howells 		if (strcmp(p->name, name) == 0)
6031d921c7SDavid Howells 			return p;
6131d921c7SDavid Howells 
6231d921c7SDavid Howells 	return NULL;
6331d921c7SDavid Howells }
6431d921c7SDavid Howells 
6531d921c7SDavid Howells /*
6631d921c7SDavid Howells  * fs_parse - Parse a filesystem configuration parameter
6731d921c7SDavid Howells  * @fc: The filesystem context to log errors through.
6831d921c7SDavid Howells  * @desc: The parameter description to use.
6931d921c7SDavid Howells  * @param: The parameter.
7031d921c7SDavid Howells  * @result: Where to place the result of the parse
7131d921c7SDavid Howells  *
7231d921c7SDavid Howells  * Parse a filesystem configuration parameter and attempt a conversion for a
7331d921c7SDavid Howells  * simple parameter for which this is requested.  If successful, the determined
7431d921c7SDavid Howells  * parameter ID is placed into @result->key, the desired type is indicated in
7531d921c7SDavid Howells  * @result->t and any converted value is placed into an appropriate member of
7631d921c7SDavid Howells  * the union in @result.
7731d921c7SDavid Howells  *
7831d921c7SDavid Howells  * The function returns the parameter number if the parameter was matched,
7931d921c7SDavid Howells  * -ENOPARAM if it wasn't matched and @desc->ignore_unknown indicated that
8031d921c7SDavid Howells  * unknown parameters are okay and -EINVAL if there was a conversion issue or
8131d921c7SDavid Howells  * the parameter wasn't recognised and unknowns aren't okay.
8231d921c7SDavid Howells  */
837f5d3814SAl Viro int __fs_parse(struct p_log *log,
8431d921c7SDavid Howells 	     const struct fs_parameter_description *desc,
8531d921c7SDavid Howells 	     struct fs_parameter *param,
8631d921c7SDavid Howells 	     struct fs_parse_result *result)
8731d921c7SDavid Howells {
8831d921c7SDavid Howells 	const struct fs_parameter_spec *p;
895eede625SAl Viro 	const struct constant_table *e;
9031d921c7SDavid Howells 	int ret = -ENOPARAM, b;
9131d921c7SDavid Howells 
9231d921c7SDavid Howells 	result->negated = false;
9331d921c7SDavid Howells 	result->uint_64 = 0;
9431d921c7SDavid Howells 
9531d921c7SDavid Howells 	p = fs_lookup_key(desc, param->key);
9631d921c7SDavid Howells 	if (!p) {
9731d921c7SDavid Howells 		/* If we didn't find something that looks like "noxxx", see if
9831d921c7SDavid Howells 		 * "xxx" takes the "no"-form negative - but only if there
9931d921c7SDavid Howells 		 * wasn't an value.
10031d921c7SDavid Howells 		 */
1010f89589aSAl Viro 		if (param->type != fs_value_is_flag)
10231d921c7SDavid Howells 			goto unknown_parameter;
10331d921c7SDavid Howells 		if (param->key[0] != 'n' || param->key[1] != 'o' || !param->key[2])
10431d921c7SDavid Howells 			goto unknown_parameter;
10531d921c7SDavid Howells 
10631d921c7SDavid Howells 		p = fs_lookup_key(desc, param->key + 2);
10731d921c7SDavid Howells 		if (!p)
10831d921c7SDavid Howells 			goto unknown_parameter;
10931d921c7SDavid Howells 		if (!(p->flags & fs_param_neg_with_no))
11031d921c7SDavid Howells 			goto unknown_parameter;
11131d921c7SDavid Howells 		result->boolean = false;
11231d921c7SDavid Howells 		result->negated = true;
11331d921c7SDavid Howells 	}
11431d921c7SDavid Howells 
11531d921c7SDavid Howells 	if (p->flags & fs_param_deprecated)
1167f5d3814SAl Viro 		warn_plog(log, "Deprecated parameter '%s'", param->key);
11731d921c7SDavid Howells 
11831d921c7SDavid Howells 	if (result->negated)
11931d921c7SDavid Howells 		goto okay;
12031d921c7SDavid Howells 
12131d921c7SDavid Howells 	/* Certain parameter types only take a string and convert it. */
12231d921c7SDavid Howells 	switch (p->type) {
12331d921c7SDavid Howells 	case __fs_param_wasnt_defined:
12431d921c7SDavid Howells 		return -EINVAL;
12531d921c7SDavid Howells 	case fs_param_is_u32:
12631d921c7SDavid Howells 	case fs_param_is_u32_octal:
12731d921c7SDavid Howells 	case fs_param_is_u32_hex:
12831d921c7SDavid Howells 	case fs_param_is_s32:
12931d921c7SDavid Howells 	case fs_param_is_u64:
13031d921c7SDavid Howells 	case fs_param_is_enum:
13131d921c7SDavid Howells 	case fs_param_is_string:
1320f89589aSAl Viro 		if (param->type == fs_value_is_string) {
1330f89589aSAl Viro 			if (p->flags & fs_param_v_optional)
1340f89589aSAl Viro 				break;
1350f89589aSAl Viro 			if (!*param->string)
13631d921c7SDavid Howells 				goto bad_value;
1370f89589aSAl Viro 			break;
1380f89589aSAl Viro 		}
1390f89589aSAl Viro 		if (param->type == fs_value_is_flag) {
14031d921c7SDavid Howells 			if (p->flags & fs_param_v_optional)
14131d921c7SDavid Howells 				goto okay;
14231d921c7SDavid Howells 		}
1430f89589aSAl Viro 		goto bad_value;
14431d921c7SDavid Howells 	default:
14531d921c7SDavid Howells 		break;
14631d921c7SDavid Howells 	}
14731d921c7SDavid Howells 
14831d921c7SDavid Howells 	/* Try to turn the type we were given into the type desired by the
14931d921c7SDavid Howells 	 * parameter and give an error if we can't.
15031d921c7SDavid Howells 	 */
15131d921c7SDavid Howells 	switch (p->type) {
15231d921c7SDavid Howells 	case fs_param_is_flag:
1530f89589aSAl Viro 		if (param->type != fs_value_is_flag)
1547f5d3814SAl Viro 			return inval_plog(log, "Unexpected value for '%s'",
1557f5d3814SAl Viro 				      param->key);
15631d921c7SDavid Howells 		result->boolean = true;
15731d921c7SDavid Howells 		goto okay;
15831d921c7SDavid Howells 
15931d921c7SDavid Howells 	case fs_param_is_bool:
16031d921c7SDavid Howells 		switch (param->type) {
16131d921c7SDavid Howells 		case fs_value_is_flag:
16231d921c7SDavid Howells 			result->boolean = true;
16331d921c7SDavid Howells 			goto okay;
16431d921c7SDavid Howells 		case fs_value_is_string:
16531d921c7SDavid Howells 			if (param->size == 0) {
16631d921c7SDavid Howells 				result->boolean = true;
16731d921c7SDavid Howells 				goto okay;
16831d921c7SDavid Howells 			}
16931d921c7SDavid Howells 			b = lookup_constant(bool_names, param->string, -1);
17031d921c7SDavid Howells 			if (b == -1)
17131d921c7SDavid Howells 				goto bad_value;
17231d921c7SDavid Howells 			result->boolean = b;
17331d921c7SDavid Howells 			goto okay;
17431d921c7SDavid Howells 		default:
17531d921c7SDavid Howells 			goto bad_value;
17631d921c7SDavid Howells 		}
17731d921c7SDavid Howells 
17831d921c7SDavid Howells 	case fs_param_is_u32:
17931d921c7SDavid Howells 		ret = kstrtouint(param->string, 0, &result->uint_32);
18031d921c7SDavid Howells 		goto maybe_okay;
18131d921c7SDavid Howells 	case fs_param_is_u32_octal:
18231d921c7SDavid Howells 		ret = kstrtouint(param->string, 8, &result->uint_32);
18331d921c7SDavid Howells 		goto maybe_okay;
18431d921c7SDavid Howells 	case fs_param_is_u32_hex:
18531d921c7SDavid Howells 		ret = kstrtouint(param->string, 16, &result->uint_32);
18631d921c7SDavid Howells 		goto maybe_okay;
18731d921c7SDavid Howells 	case fs_param_is_s32:
18831d921c7SDavid Howells 		ret = kstrtoint(param->string, 0, &result->int_32);
18931d921c7SDavid Howells 		goto maybe_okay;
19031d921c7SDavid Howells 	case fs_param_is_u64:
19131d921c7SDavid Howells 		ret = kstrtoull(param->string, 0, &result->uint_64);
19231d921c7SDavid Howells 		goto maybe_okay;
19331d921c7SDavid Howells 
19431d921c7SDavid Howells 	case fs_param_is_enum:
19534264ae3SAl Viro 		e = __lookup_constant(p->data, param->string);
19634264ae3SAl Viro 		if (e) {
19731d921c7SDavid Howells 			result->uint_32 = e->value;
19831d921c7SDavid Howells 			goto okay;
19931d921c7SDavid Howells 		}
20031d921c7SDavid Howells 		goto bad_value;
20131d921c7SDavid Howells 
20231d921c7SDavid Howells 	case fs_param_is_string:
20331d921c7SDavid Howells 		goto okay;
20431d921c7SDavid Howells 	case fs_param_is_blob:
20531d921c7SDavid Howells 		if (param->type != fs_value_is_blob)
20631d921c7SDavid Howells 			goto bad_value;
20731d921c7SDavid Howells 		goto okay;
20831d921c7SDavid Howells 
20931d921c7SDavid Howells 	case fs_param_is_fd: {
21074983ac2SDavid Howells 		switch (param->type) {
21174983ac2SDavid Howells 		case fs_value_is_string:
21274983ac2SDavid Howells 			ret = kstrtouint(param->string, 0, &result->uint_32);
21374983ac2SDavid Howells 			break;
21474983ac2SDavid Howells 		case fs_value_is_file:
21574983ac2SDavid Howells 			result->uint_32 = param->dirfd;
21674983ac2SDavid Howells 			ret = 0;
21774983ac2SDavid Howells 		default:
21874983ac2SDavid Howells 			goto bad_value;
21974983ac2SDavid Howells 		}
22074983ac2SDavid Howells 
22174983ac2SDavid Howells 		if (result->uint_32 > INT_MAX)
22274983ac2SDavid Howells 			goto bad_value;
22374983ac2SDavid Howells 		goto maybe_okay;
22431d921c7SDavid Howells 	}
22531d921c7SDavid Howells 
22631d921c7SDavid Howells 	case fs_param_is_blockdev:
22731d921c7SDavid Howells 	case fs_param_is_path:
22831d921c7SDavid Howells 		goto okay;
22931d921c7SDavid Howells 	default:
23031d921c7SDavid Howells 		BUG();
23131d921c7SDavid Howells 	}
23231d921c7SDavid Howells 
23331d921c7SDavid Howells maybe_okay:
23431d921c7SDavid Howells 	if (ret < 0)
23531d921c7SDavid Howells 		goto bad_value;
23631d921c7SDavid Howells okay:
23731d921c7SDavid Howells 	return p->opt;
23831d921c7SDavid Howells 
23931d921c7SDavid Howells bad_value:
2407f5d3814SAl Viro 	return inval_plog(log, "Bad value for '%s'", param->key);
24131d921c7SDavid Howells unknown_parameter:
24231d921c7SDavid Howells 	return -ENOPARAM;
24331d921c7SDavid Howells }
2447f5d3814SAl Viro EXPORT_SYMBOL(__fs_parse);
2457f5d3814SAl Viro 
24631d921c7SDavid Howells /**
24731d921c7SDavid Howells  * fs_lookup_param - Look up a path referred to by a parameter
24831d921c7SDavid Howells  * @fc: The filesystem context to log errors through.
24931d921c7SDavid Howells  * @param: The parameter.
25031d921c7SDavid Howells  * @want_bdev: T if want a blockdev
25131d921c7SDavid Howells  * @_path: The result of the lookup
25231d921c7SDavid Howells  */
25331d921c7SDavid Howells int fs_lookup_param(struct fs_context *fc,
25431d921c7SDavid Howells 		    struct fs_parameter *param,
25531d921c7SDavid Howells 		    bool want_bdev,
25631d921c7SDavid Howells 		    struct path *_path)
25731d921c7SDavid Howells {
25831d921c7SDavid Howells 	struct filename *f;
25931d921c7SDavid Howells 	unsigned int flags = 0;
26031d921c7SDavid Howells 	bool put_f;
26131d921c7SDavid Howells 	int ret;
26231d921c7SDavid Howells 
26331d921c7SDavid Howells 	switch (param->type) {
26431d921c7SDavid Howells 	case fs_value_is_string:
26531d921c7SDavid Howells 		f = getname_kernel(param->string);
26631d921c7SDavid Howells 		if (IS_ERR(f))
26731d921c7SDavid Howells 			return PTR_ERR(f);
26831d921c7SDavid Howells 		put_f = true;
26931d921c7SDavid Howells 		break;
27031d921c7SDavid Howells 	case fs_value_is_filename:
27131d921c7SDavid Howells 		f = param->name;
27231d921c7SDavid Howells 		put_f = false;
27331d921c7SDavid Howells 		break;
27431d921c7SDavid Howells 	default:
27531d921c7SDavid Howells 		return invalf(fc, "%s: not usable as path", param->key);
27631d921c7SDavid Howells 	}
27731d921c7SDavid Howells 
2787cdfa442SDavid Howells 	f->refcnt++; /* filename_lookup() drops our ref. */
27931d921c7SDavid Howells 	ret = filename_lookup(param->dirfd, f, flags, _path, NULL);
28031d921c7SDavid Howells 	if (ret < 0) {
28131d921c7SDavid Howells 		errorf(fc, "%s: Lookup failure for '%s'", param->key, f->name);
28231d921c7SDavid Howells 		goto out;
28331d921c7SDavid Howells 	}
28431d921c7SDavid Howells 
28531d921c7SDavid Howells 	if (want_bdev &&
28631d921c7SDavid Howells 	    !S_ISBLK(d_backing_inode(_path->dentry)->i_mode)) {
28731d921c7SDavid Howells 		path_put(_path);
28831d921c7SDavid Howells 		_path->dentry = NULL;
28931d921c7SDavid Howells 		_path->mnt = NULL;
29031d921c7SDavid Howells 		errorf(fc, "%s: Non-blockdev passed as '%s'",
29131d921c7SDavid Howells 		       param->key, f->name);
29231d921c7SDavid Howells 		ret = -ENOTBLK;
29331d921c7SDavid Howells 	}
29431d921c7SDavid Howells 
29531d921c7SDavid Howells out:
29631d921c7SDavid Howells 	if (put_f)
29731d921c7SDavid Howells 		putname(f);
29831d921c7SDavid Howells 	return ret;
29931d921c7SDavid Howells }
30031d921c7SDavid Howells EXPORT_SYMBOL(fs_lookup_param);
30131d921c7SDavid Howells 
30231d921c7SDavid Howells #ifdef CONFIG_VALIDATE_FS_PARSER
30331d921c7SDavid Howells /**
30431d921c7SDavid Howells  * validate_constant_table - Validate a constant table
30531d921c7SDavid Howells  * @name: Name to use in reporting
30631d921c7SDavid Howells  * @tbl: The constant table to validate.
30731d921c7SDavid Howells  * @tbl_size: The size of the table.
30831d921c7SDavid Howells  * @low: The lowest permissible value.
30931d921c7SDavid Howells  * @high: The highest permissible value.
31031d921c7SDavid Howells  * @special: One special permissible value outside of the range.
31131d921c7SDavid Howells  */
31231d921c7SDavid Howells bool validate_constant_table(const struct constant_table *tbl, size_t tbl_size,
31331d921c7SDavid Howells 			     int low, int high, int special)
31431d921c7SDavid Howells {
31531d921c7SDavid Howells 	size_t i;
31631d921c7SDavid Howells 	bool good = true;
31731d921c7SDavid Howells 
31831d921c7SDavid Howells 	if (tbl_size == 0) {
31931d921c7SDavid Howells 		pr_warn("VALIDATE C-TBL: Empty\n");
32031d921c7SDavid Howells 		return true;
32131d921c7SDavid Howells 	}
32231d921c7SDavid Howells 
32331d921c7SDavid Howells 	for (i = 0; i < tbl_size; i++) {
32431d921c7SDavid Howells 		if (!tbl[i].name) {
32531d921c7SDavid Howells 			pr_err("VALIDATE C-TBL[%zu]: Null\n", i);
32631d921c7SDavid Howells 			good = false;
32731d921c7SDavid Howells 		} else if (i > 0 && tbl[i - 1].name) {
32831d921c7SDavid Howells 			int c = strcmp(tbl[i-1].name, tbl[i].name);
32931d921c7SDavid Howells 
33031d921c7SDavid Howells 			if (c == 0) {
33131d921c7SDavid Howells 				pr_err("VALIDATE C-TBL[%zu]: Duplicate %s\n",
33231d921c7SDavid Howells 				       i, tbl[i].name);
33331d921c7SDavid Howells 				good = false;
33431d921c7SDavid Howells 			}
33531d921c7SDavid Howells 			if (c > 0) {
33631d921c7SDavid Howells 				pr_err("VALIDATE C-TBL[%zu]: Missorted %s>=%s\n",
33731d921c7SDavid Howells 				       i, tbl[i-1].name, tbl[i].name);
33831d921c7SDavid Howells 				good = false;
33931d921c7SDavid Howells 			}
34031d921c7SDavid Howells 		}
34131d921c7SDavid Howells 
34231d921c7SDavid Howells 		if (tbl[i].value != special &&
34331d921c7SDavid Howells 		    (tbl[i].value < low || tbl[i].value > high)) {
34431d921c7SDavid Howells 			pr_err("VALIDATE C-TBL[%zu]: %s->%d const out of range (%d-%d)\n",
34531d921c7SDavid Howells 			       i, tbl[i].name, tbl[i].value, low, high);
34631d921c7SDavid Howells 			good = false;
34731d921c7SDavid Howells 		}
34831d921c7SDavid Howells 	}
34931d921c7SDavid Howells 
35031d921c7SDavid Howells 	return good;
35131d921c7SDavid Howells }
35231d921c7SDavid Howells 
35331d921c7SDavid Howells /**
35431d921c7SDavid Howells  * fs_validate_description - Validate a parameter description
35531d921c7SDavid Howells  * @desc: The parameter description to validate.
35631d921c7SDavid Howells  */
357*96cafb9cSEric Sandeen bool fs_validate_description(const char *name,
358*96cafb9cSEric Sandeen 	const struct fs_parameter_description *desc)
35931d921c7SDavid Howells {
36031d921c7SDavid Howells 	const struct fs_parameter_spec *param, *p2;
3612710c957SAl Viro 	bool good = true;
36231d921c7SDavid Howells 
36331d921c7SDavid Howells 	pr_notice("*** VALIDATE %s ***\n", name);
36431d921c7SDavid Howells 
36531d921c7SDavid Howells 	if (desc->specs) {
36631d921c7SDavid Howells 		for (param = desc->specs; param->name; param++) {
36731d921c7SDavid Howells 			enum fs_parameter_type t = param->type;
36831d921c7SDavid Howells 
36931d921c7SDavid Howells 			/* Check that the type is in range */
37031d921c7SDavid Howells 			if (t == __fs_param_wasnt_defined ||
37131d921c7SDavid Howells 			    t >= nr__fs_parameter_type) {
37231d921c7SDavid Howells 				pr_err("VALIDATE %s: PARAM[%s] Bad type %u\n",
37331d921c7SDavid Howells 				       name, param->name, t);
37431d921c7SDavid Howells 				good = false;
37531d921c7SDavid Howells 			} else if (t == fs_param_is_enum) {
3765eede625SAl Viro 				const struct constant_table *e = param->data;
3772710c957SAl Viro 				if (!e || !e->name) {
3782710c957SAl Viro 					pr_err("VALIDATE %s: PARAM[%s] enum with no values\n",
3792710c957SAl Viro 					       name, param->name);
3802710c957SAl Viro 					good = false;
3812710c957SAl Viro 				}
38231d921c7SDavid Howells 			}
38331d921c7SDavid Howells 
38431d921c7SDavid Howells 			/* Check for duplicate parameter names */
38531d921c7SDavid Howells 			for (p2 = desc->specs; p2 < param; p2++) {
38631d921c7SDavid Howells 				if (strcmp(param->name, p2->name) == 0) {
38731d921c7SDavid Howells 					pr_err("VALIDATE %s: PARAM[%s]: Duplicate\n",
38831d921c7SDavid Howells 					       name, param->name);
38931d921c7SDavid Howells 					good = false;
39031d921c7SDavid Howells 				}
39131d921c7SDavid Howells 			}
39231d921c7SDavid Howells 		}
39331d921c7SDavid Howells 	}
39431d921c7SDavid Howells 	return good;
39531d921c7SDavid Howells }
39631d921c7SDavid Howells #endif /* CONFIG_VALIDATE_FS_PARSER */
397