xref: /openbmc/linux/fs/fs_parser.c (revision 9726bfcd)
1  // SPDX-License-Identifier: GPL-2.0-or-later
2  /* Filesystem parameter parser.
3   *
4   * Copyright (C) 2018 Red Hat, Inc. All Rights Reserved.
5   * Written by David Howells (dhowells@redhat.com)
6   */
7  
8  #include <linux/export.h>
9  #include <linux/fs_context.h>
10  #include <linux/fs_parser.h>
11  #include <linux/slab.h>
12  #include <linux/security.h>
13  #include <linux/namei.h>
14  #include "internal.h"
15  
16  static const struct constant_table bool_names[] = {
17  	{ "0",		false },
18  	{ "1",		true },
19  	{ "false",	false },
20  	{ "no",		false },
21  	{ "true",	true },
22  	{ "yes",	true },
23  };
24  
25  /**
26   * lookup_constant - Look up a constant by name in an ordered table
27   * @tbl: The table of constants to search.
28   * @tbl_size: The size of the table.
29   * @name: The name to look up.
30   * @not_found: The value to return if the name is not found.
31   */
32  int __lookup_constant(const struct constant_table *tbl, size_t tbl_size,
33  		      const char *name, int not_found)
34  {
35  	unsigned int i;
36  
37  	for (i = 0; i < tbl_size; i++)
38  		if (strcmp(name, tbl[i].name) == 0)
39  			return tbl[i].value;
40  
41  	return not_found;
42  }
43  EXPORT_SYMBOL(__lookup_constant);
44  
45  static const struct fs_parameter_spec *fs_lookup_key(
46  	const struct fs_parameter_description *desc,
47  	const char *name)
48  {
49  	const struct fs_parameter_spec *p;
50  
51  	if (!desc->specs)
52  		return NULL;
53  
54  	for (p = desc->specs; p->name; p++)
55  		if (strcmp(p->name, name) == 0)
56  			return p;
57  
58  	return NULL;
59  }
60  
61  /*
62   * fs_parse - Parse a filesystem configuration parameter
63   * @fc: The filesystem context to log errors through.
64   * @desc: The parameter description to use.
65   * @param: The parameter.
66   * @result: Where to place the result of the parse
67   *
68   * Parse a filesystem configuration parameter and attempt a conversion for a
69   * simple parameter for which this is requested.  If successful, the determined
70   * parameter ID is placed into @result->key, the desired type is indicated in
71   * @result->t and any converted value is placed into an appropriate member of
72   * the union in @result.
73   *
74   * The function returns the parameter number if the parameter was matched,
75   * -ENOPARAM if it wasn't matched and @desc->ignore_unknown indicated that
76   * unknown parameters are okay and -EINVAL if there was a conversion issue or
77   * the parameter wasn't recognised and unknowns aren't okay.
78   */
79  int fs_parse(struct fs_context *fc,
80  	     const struct fs_parameter_description *desc,
81  	     struct fs_parameter *param,
82  	     struct fs_parse_result *result)
83  {
84  	const struct fs_parameter_spec *p;
85  	const struct fs_parameter_enum *e;
86  	int ret = -ENOPARAM, b;
87  
88  	result->has_value = !!param->string;
89  	result->negated = false;
90  	result->uint_64 = 0;
91  
92  	p = fs_lookup_key(desc, param->key);
93  	if (!p) {
94  		/* If we didn't find something that looks like "noxxx", see if
95  		 * "xxx" takes the "no"-form negative - but only if there
96  		 * wasn't an value.
97  		 */
98  		if (result->has_value)
99  			goto unknown_parameter;
100  		if (param->key[0] != 'n' || param->key[1] != 'o' || !param->key[2])
101  			goto unknown_parameter;
102  
103  		p = fs_lookup_key(desc, param->key + 2);
104  		if (!p)
105  			goto unknown_parameter;
106  		if (!(p->flags & fs_param_neg_with_no))
107  			goto unknown_parameter;
108  		result->boolean = false;
109  		result->negated = true;
110  	}
111  
112  	if (p->flags & fs_param_deprecated)
113  		warnf(fc, "%s: Deprecated parameter '%s'",
114  		      desc->name, param->key);
115  
116  	if (result->negated)
117  		goto okay;
118  
119  	/* Certain parameter types only take a string and convert it. */
120  	switch (p->type) {
121  	case __fs_param_wasnt_defined:
122  		return -EINVAL;
123  	case fs_param_is_u32:
124  	case fs_param_is_u32_octal:
125  	case fs_param_is_u32_hex:
126  	case fs_param_is_s32:
127  	case fs_param_is_u64:
128  	case fs_param_is_enum:
129  	case fs_param_is_string:
130  		if (param->type != fs_value_is_string)
131  			goto bad_value;
132  		if (!result->has_value) {
133  			if (p->flags & fs_param_v_optional)
134  				goto okay;
135  			goto bad_value;
136  		}
137  		/* Fall through */
138  	default:
139  		break;
140  	}
141  
142  	/* Try to turn the type we were given into the type desired by the
143  	 * parameter and give an error if we can't.
144  	 */
145  	switch (p->type) {
146  	case fs_param_is_flag:
147  		if (param->type != fs_value_is_flag &&
148  		    (param->type != fs_value_is_string || result->has_value))
149  			return invalf(fc, "%s: Unexpected value for '%s'",
150  				      desc->name, param->key);
151  		result->boolean = true;
152  		goto okay;
153  
154  	case fs_param_is_bool:
155  		switch (param->type) {
156  		case fs_value_is_flag:
157  			result->boolean = true;
158  			goto okay;
159  		case fs_value_is_string:
160  			if (param->size == 0) {
161  				result->boolean = true;
162  				goto okay;
163  			}
164  			b = lookup_constant(bool_names, param->string, -1);
165  			if (b == -1)
166  				goto bad_value;
167  			result->boolean = b;
168  			goto okay;
169  		default:
170  			goto bad_value;
171  		}
172  
173  	case fs_param_is_u32:
174  		ret = kstrtouint(param->string, 0, &result->uint_32);
175  		goto maybe_okay;
176  	case fs_param_is_u32_octal:
177  		ret = kstrtouint(param->string, 8, &result->uint_32);
178  		goto maybe_okay;
179  	case fs_param_is_u32_hex:
180  		ret = kstrtouint(param->string, 16, &result->uint_32);
181  		goto maybe_okay;
182  	case fs_param_is_s32:
183  		ret = kstrtoint(param->string, 0, &result->int_32);
184  		goto maybe_okay;
185  	case fs_param_is_u64:
186  		ret = kstrtoull(param->string, 0, &result->uint_64);
187  		goto maybe_okay;
188  
189  	case fs_param_is_enum:
190  		for (e = desc->enums; e->name[0]; e++) {
191  			if (e->opt == p->opt &&
192  			    strcmp(e->name, param->string) == 0) {
193  				result->uint_32 = e->value;
194  				goto okay;
195  			}
196  		}
197  		goto bad_value;
198  
199  	case fs_param_is_string:
200  		goto okay;
201  	case fs_param_is_blob:
202  		if (param->type != fs_value_is_blob)
203  			goto bad_value;
204  		goto okay;
205  
206  	case fs_param_is_fd: {
207  		if (param->type != fs_value_is_file)
208  			goto bad_value;
209  		goto okay;
210  	}
211  
212  	case fs_param_is_blockdev:
213  	case fs_param_is_path:
214  		goto okay;
215  	default:
216  		BUG();
217  	}
218  
219  maybe_okay:
220  	if (ret < 0)
221  		goto bad_value;
222  okay:
223  	return p->opt;
224  
225  bad_value:
226  	return invalf(fc, "%s: Bad value for '%s'", desc->name, param->key);
227  unknown_parameter:
228  	return -ENOPARAM;
229  }
230  EXPORT_SYMBOL(fs_parse);
231  
232  /**
233   * fs_lookup_param - Look up a path referred to by a parameter
234   * @fc: The filesystem context to log errors through.
235   * @param: The parameter.
236   * @want_bdev: T if want a blockdev
237   * @_path: The result of the lookup
238   */
239  int fs_lookup_param(struct fs_context *fc,
240  		    struct fs_parameter *param,
241  		    bool want_bdev,
242  		    struct path *_path)
243  {
244  	struct filename *f;
245  	unsigned int flags = 0;
246  	bool put_f;
247  	int ret;
248  
249  	switch (param->type) {
250  	case fs_value_is_string:
251  		f = getname_kernel(param->string);
252  		if (IS_ERR(f))
253  			return PTR_ERR(f);
254  		put_f = true;
255  		break;
256  	case fs_value_is_filename_empty:
257  		flags = LOOKUP_EMPTY;
258  		/* Fall through */
259  	case fs_value_is_filename:
260  		f = param->name;
261  		put_f = false;
262  		break;
263  	default:
264  		return invalf(fc, "%s: not usable as path", param->key);
265  	}
266  
267  	f->refcnt++; /* filename_lookup() drops our ref. */
268  	ret = filename_lookup(param->dirfd, f, flags, _path, NULL);
269  	if (ret < 0) {
270  		errorf(fc, "%s: Lookup failure for '%s'", param->key, f->name);
271  		goto out;
272  	}
273  
274  	if (want_bdev &&
275  	    !S_ISBLK(d_backing_inode(_path->dentry)->i_mode)) {
276  		path_put(_path);
277  		_path->dentry = NULL;
278  		_path->mnt = NULL;
279  		errorf(fc, "%s: Non-blockdev passed as '%s'",
280  		       param->key, f->name);
281  		ret = -ENOTBLK;
282  	}
283  
284  out:
285  	if (put_f)
286  		putname(f);
287  	return ret;
288  }
289  EXPORT_SYMBOL(fs_lookup_param);
290  
291  #ifdef CONFIG_VALIDATE_FS_PARSER
292  /**
293   * validate_constant_table - Validate a constant table
294   * @name: Name to use in reporting
295   * @tbl: The constant table to validate.
296   * @tbl_size: The size of the table.
297   * @low: The lowest permissible value.
298   * @high: The highest permissible value.
299   * @special: One special permissible value outside of the range.
300   */
301  bool validate_constant_table(const struct constant_table *tbl, size_t tbl_size,
302  			     int low, int high, int special)
303  {
304  	size_t i;
305  	bool good = true;
306  
307  	if (tbl_size == 0) {
308  		pr_warn("VALIDATE C-TBL: Empty\n");
309  		return true;
310  	}
311  
312  	for (i = 0; i < tbl_size; i++) {
313  		if (!tbl[i].name) {
314  			pr_err("VALIDATE C-TBL[%zu]: Null\n", i);
315  			good = false;
316  		} else if (i > 0 && tbl[i - 1].name) {
317  			int c = strcmp(tbl[i-1].name, tbl[i].name);
318  
319  			if (c == 0) {
320  				pr_err("VALIDATE C-TBL[%zu]: Duplicate %s\n",
321  				       i, tbl[i].name);
322  				good = false;
323  			}
324  			if (c > 0) {
325  				pr_err("VALIDATE C-TBL[%zu]: Missorted %s>=%s\n",
326  				       i, tbl[i-1].name, tbl[i].name);
327  				good = false;
328  			}
329  		}
330  
331  		if (tbl[i].value != special &&
332  		    (tbl[i].value < low || tbl[i].value > high)) {
333  			pr_err("VALIDATE C-TBL[%zu]: %s->%d const out of range (%d-%d)\n",
334  			       i, tbl[i].name, tbl[i].value, low, high);
335  			good = false;
336  		}
337  	}
338  
339  	return good;
340  }
341  
342  /**
343   * fs_validate_description - Validate a parameter description
344   * @desc: The parameter description to validate.
345   */
346  bool fs_validate_description(const struct fs_parameter_description *desc)
347  {
348  	const struct fs_parameter_spec *param, *p2;
349  	const struct fs_parameter_enum *e;
350  	const char *name = desc->name;
351  	unsigned int nr_params = 0;
352  	bool good = true, enums = false;
353  
354  	pr_notice("*** VALIDATE %s ***\n", name);
355  
356  	if (!name[0]) {
357  		pr_err("VALIDATE Parser: No name\n");
358  		name = "Unknown";
359  		good = false;
360  	}
361  
362  	if (desc->specs) {
363  		for (param = desc->specs; param->name; param++) {
364  			enum fs_parameter_type t = param->type;
365  
366  			/* Check that the type is in range */
367  			if (t == __fs_param_wasnt_defined ||
368  			    t >= nr__fs_parameter_type) {
369  				pr_err("VALIDATE %s: PARAM[%s] Bad type %u\n",
370  				       name, param->name, t);
371  				good = false;
372  			} else if (t == fs_param_is_enum) {
373  				enums = true;
374  			}
375  
376  			/* Check for duplicate parameter names */
377  			for (p2 = desc->specs; p2 < param; p2++) {
378  				if (strcmp(param->name, p2->name) == 0) {
379  					pr_err("VALIDATE %s: PARAM[%s]: Duplicate\n",
380  					       name, param->name);
381  					good = false;
382  				}
383  			}
384  		}
385  
386  		nr_params = param - desc->specs;
387  	}
388  
389  	if (desc->enums) {
390  		if (!nr_params) {
391  			pr_err("VALIDATE %s: Enum table but no parameters\n",
392  			       name);
393  			good = false;
394  			goto no_enums;
395  		}
396  		if (!enums) {
397  			pr_err("VALIDATE %s: Enum table but no enum-type values\n",
398  			       name);
399  			good = false;
400  			goto no_enums;
401  		}
402  
403  		for (e = desc->enums; e->name[0]; e++) {
404  			/* Check that all entries in the enum table have at
405  			 * least one parameter that uses them.
406  			 */
407  			for (param = desc->specs; param->name; param++) {
408  				if (param->opt == e->opt &&
409  				    param->type != fs_param_is_enum) {
410  					pr_err("VALIDATE %s: e[%tu] enum val for %s\n",
411  					       name, e - desc->enums, param->name);
412  					good = false;
413  				}
414  			}
415  		}
416  
417  		/* Check that all enum-type parameters have at least one enum
418  		 * value in the enum table.
419  		 */
420  		for (param = desc->specs; param->name; param++) {
421  			if (param->type != fs_param_is_enum)
422  				continue;
423  			for (e = desc->enums; e->name[0]; e++)
424  				if (e->opt == param->opt)
425  					break;
426  			if (!e->name[0]) {
427  				pr_err("VALIDATE %s: PARAM[%s] enum with no values\n",
428  				       name, param->name);
429  				good = false;
430  			}
431  		}
432  	} else {
433  		if (enums) {
434  			pr_err("VALIDATE %s: enum-type values, but no enum table\n",
435  			       name);
436  			good = false;
437  			goto no_enums;
438  		}
439  	}
440  
441  no_enums:
442  	return good;
443  }
444  #endif /* CONFIG_VALIDATE_FS_PARSER */
445