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