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 }, 2331d921c7SDavid Howells }; 2431d921c7SDavid Howells 2531d921c7SDavid Howells /** 2631d921c7SDavid Howells * lookup_constant - Look up a constant by name in an ordered table 2731d921c7SDavid Howells * @tbl: The table of constants to search. 2831d921c7SDavid Howells * @tbl_size: The size of the table. 2931d921c7SDavid Howells * @name: The name to look up. 3031d921c7SDavid Howells * @not_found: The value to return if the name is not found. 3131d921c7SDavid Howells */ 3231d921c7SDavid Howells int __lookup_constant(const struct constant_table *tbl, size_t tbl_size, 3331d921c7SDavid Howells const char *name, int not_found) 3431d921c7SDavid Howells { 3531d921c7SDavid Howells unsigned int i; 3631d921c7SDavid Howells 3731d921c7SDavid Howells for (i = 0; i < tbl_size; i++) 3831d921c7SDavid Howells if (strcmp(name, tbl[i].name) == 0) 3931d921c7SDavid Howells return tbl[i].value; 4031d921c7SDavid Howells 4131d921c7SDavid Howells return not_found; 4231d921c7SDavid Howells } 4331d921c7SDavid Howells EXPORT_SYMBOL(__lookup_constant); 4431d921c7SDavid Howells 4531d921c7SDavid Howells static const struct fs_parameter_spec *fs_lookup_key( 4631d921c7SDavid Howells const struct fs_parameter_description *desc, 4731d921c7SDavid Howells const char *name) 4831d921c7SDavid Howells { 4931d921c7SDavid Howells const struct fs_parameter_spec *p; 5031d921c7SDavid Howells 5131d921c7SDavid Howells if (!desc->specs) 5231d921c7SDavid Howells return NULL; 5331d921c7SDavid Howells 5431d921c7SDavid Howells for (p = desc->specs; p->name; p++) 5531d921c7SDavid Howells if (strcmp(p->name, name) == 0) 5631d921c7SDavid Howells return p; 5731d921c7SDavid Howells 5831d921c7SDavid Howells return NULL; 5931d921c7SDavid Howells } 6031d921c7SDavid Howells 6131d921c7SDavid Howells /* 6231d921c7SDavid Howells * fs_parse - Parse a filesystem configuration parameter 6331d921c7SDavid Howells * @fc: The filesystem context to log errors through. 6431d921c7SDavid Howells * @desc: The parameter description to use. 6531d921c7SDavid Howells * @param: The parameter. 6631d921c7SDavid Howells * @result: Where to place the result of the parse 6731d921c7SDavid Howells * 6831d921c7SDavid Howells * Parse a filesystem configuration parameter and attempt a conversion for a 6931d921c7SDavid Howells * simple parameter for which this is requested. If successful, the determined 7031d921c7SDavid Howells * parameter ID is placed into @result->key, the desired type is indicated in 7131d921c7SDavid Howells * @result->t and any converted value is placed into an appropriate member of 7231d921c7SDavid Howells * the union in @result. 7331d921c7SDavid Howells * 7431d921c7SDavid Howells * The function returns the parameter number if the parameter was matched, 7531d921c7SDavid Howells * -ENOPARAM if it wasn't matched and @desc->ignore_unknown indicated that 7631d921c7SDavid Howells * unknown parameters are okay and -EINVAL if there was a conversion issue or 7731d921c7SDavid Howells * the parameter wasn't recognised and unknowns aren't okay. 7831d921c7SDavid Howells */ 7931d921c7SDavid Howells int fs_parse(struct fs_context *fc, 8031d921c7SDavid Howells const struct fs_parameter_description *desc, 8131d921c7SDavid Howells struct fs_parameter *param, 8231d921c7SDavid Howells struct fs_parse_result *result) 8331d921c7SDavid Howells { 8431d921c7SDavid Howells const struct fs_parameter_spec *p; 85*5eede625SAl Viro const struct constant_table *e; 8631d921c7SDavid Howells int ret = -ENOPARAM, b; 8731d921c7SDavid Howells 8831d921c7SDavid Howells result->negated = false; 8931d921c7SDavid Howells result->uint_64 = 0; 9031d921c7SDavid Howells 9131d921c7SDavid Howells p = fs_lookup_key(desc, param->key); 9231d921c7SDavid Howells if (!p) { 9331d921c7SDavid Howells /* If we didn't find something that looks like "noxxx", see if 9431d921c7SDavid Howells * "xxx" takes the "no"-form negative - but only if there 9531d921c7SDavid Howells * wasn't an value. 9631d921c7SDavid Howells */ 970f89589aSAl Viro if (param->type != fs_value_is_flag) 9831d921c7SDavid Howells goto unknown_parameter; 9931d921c7SDavid Howells if (param->key[0] != 'n' || param->key[1] != 'o' || !param->key[2]) 10031d921c7SDavid Howells goto unknown_parameter; 10131d921c7SDavid Howells 10231d921c7SDavid Howells p = fs_lookup_key(desc, param->key + 2); 10331d921c7SDavid Howells if (!p) 10431d921c7SDavid Howells goto unknown_parameter; 10531d921c7SDavid Howells if (!(p->flags & fs_param_neg_with_no)) 10631d921c7SDavid Howells goto unknown_parameter; 10731d921c7SDavid Howells result->boolean = false; 10831d921c7SDavid Howells result->negated = true; 10931d921c7SDavid Howells } 11031d921c7SDavid Howells 11131d921c7SDavid Howells if (p->flags & fs_param_deprecated) 11231d921c7SDavid Howells warnf(fc, "%s: Deprecated parameter '%s'", 11331d921c7SDavid Howells desc->name, param->key); 11431d921c7SDavid Howells 11531d921c7SDavid Howells if (result->negated) 11631d921c7SDavid Howells goto okay; 11731d921c7SDavid Howells 11831d921c7SDavid Howells /* Certain parameter types only take a string and convert it. */ 11931d921c7SDavid Howells switch (p->type) { 12031d921c7SDavid Howells case __fs_param_wasnt_defined: 12131d921c7SDavid Howells return -EINVAL; 12231d921c7SDavid Howells case fs_param_is_u32: 12331d921c7SDavid Howells case fs_param_is_u32_octal: 12431d921c7SDavid Howells case fs_param_is_u32_hex: 12531d921c7SDavid Howells case fs_param_is_s32: 12631d921c7SDavid Howells case fs_param_is_u64: 12731d921c7SDavid Howells case fs_param_is_enum: 12831d921c7SDavid Howells case fs_param_is_string: 1290f89589aSAl Viro if (param->type == fs_value_is_string) { 1300f89589aSAl Viro if (p->flags & fs_param_v_optional) 1310f89589aSAl Viro break; 1320f89589aSAl Viro if (!*param->string) 13331d921c7SDavid Howells goto bad_value; 1340f89589aSAl Viro break; 1350f89589aSAl Viro } 1360f89589aSAl Viro if (param->type == fs_value_is_flag) { 13731d921c7SDavid Howells if (p->flags & fs_param_v_optional) 13831d921c7SDavid Howells goto okay; 13931d921c7SDavid Howells } 1400f89589aSAl Viro goto bad_value; 14131d921c7SDavid Howells default: 14231d921c7SDavid Howells break; 14331d921c7SDavid Howells } 14431d921c7SDavid Howells 14531d921c7SDavid Howells /* Try to turn the type we were given into the type desired by the 14631d921c7SDavid Howells * parameter and give an error if we can't. 14731d921c7SDavid Howells */ 14831d921c7SDavid Howells switch (p->type) { 14931d921c7SDavid Howells case fs_param_is_flag: 1500f89589aSAl Viro if (param->type != fs_value_is_flag) 15131d921c7SDavid Howells return invalf(fc, "%s: Unexpected value for '%s'", 15231d921c7SDavid Howells desc->name, param->key); 15331d921c7SDavid Howells result->boolean = true; 15431d921c7SDavid Howells goto okay; 15531d921c7SDavid Howells 15631d921c7SDavid Howells case fs_param_is_bool: 15731d921c7SDavid Howells switch (param->type) { 15831d921c7SDavid Howells case fs_value_is_flag: 15931d921c7SDavid Howells result->boolean = true; 16031d921c7SDavid Howells goto okay; 16131d921c7SDavid Howells case fs_value_is_string: 16231d921c7SDavid Howells if (param->size == 0) { 16331d921c7SDavid Howells result->boolean = true; 16431d921c7SDavid Howells goto okay; 16531d921c7SDavid Howells } 16631d921c7SDavid Howells b = lookup_constant(bool_names, param->string, -1); 16731d921c7SDavid Howells if (b == -1) 16831d921c7SDavid Howells goto bad_value; 16931d921c7SDavid Howells result->boolean = b; 17031d921c7SDavid Howells goto okay; 17131d921c7SDavid Howells default: 17231d921c7SDavid Howells goto bad_value; 17331d921c7SDavid Howells } 17431d921c7SDavid Howells 17531d921c7SDavid Howells case fs_param_is_u32: 17631d921c7SDavid Howells ret = kstrtouint(param->string, 0, &result->uint_32); 17731d921c7SDavid Howells goto maybe_okay; 17831d921c7SDavid Howells case fs_param_is_u32_octal: 17931d921c7SDavid Howells ret = kstrtouint(param->string, 8, &result->uint_32); 18031d921c7SDavid Howells goto maybe_okay; 18131d921c7SDavid Howells case fs_param_is_u32_hex: 18231d921c7SDavid Howells ret = kstrtouint(param->string, 16, &result->uint_32); 18331d921c7SDavid Howells goto maybe_okay; 18431d921c7SDavid Howells case fs_param_is_s32: 18531d921c7SDavid Howells ret = kstrtoint(param->string, 0, &result->int_32); 18631d921c7SDavid Howells goto maybe_okay; 18731d921c7SDavid Howells case fs_param_is_u64: 18831d921c7SDavid Howells ret = kstrtoull(param->string, 0, &result->uint_64); 18931d921c7SDavid Howells goto maybe_okay; 19031d921c7SDavid Howells 19131d921c7SDavid Howells case fs_param_is_enum: 1922710c957SAl Viro for (e = p->data; e->name; e++) { 1932710c957SAl Viro if (strcmp(e->name, param->string) == 0) { 19431d921c7SDavid Howells result->uint_32 = e->value; 19531d921c7SDavid Howells goto okay; 19631d921c7SDavid Howells } 19731d921c7SDavid Howells } 19831d921c7SDavid Howells goto bad_value; 19931d921c7SDavid Howells 20031d921c7SDavid Howells case fs_param_is_string: 20131d921c7SDavid Howells goto okay; 20231d921c7SDavid Howells case fs_param_is_blob: 20331d921c7SDavid Howells if (param->type != fs_value_is_blob) 20431d921c7SDavid Howells goto bad_value; 20531d921c7SDavid Howells goto okay; 20631d921c7SDavid Howells 20731d921c7SDavid Howells case fs_param_is_fd: { 20874983ac2SDavid Howells switch (param->type) { 20974983ac2SDavid Howells case fs_value_is_string: 21074983ac2SDavid Howells ret = kstrtouint(param->string, 0, &result->uint_32); 21174983ac2SDavid Howells break; 21274983ac2SDavid Howells case fs_value_is_file: 21374983ac2SDavid Howells result->uint_32 = param->dirfd; 21474983ac2SDavid Howells ret = 0; 21574983ac2SDavid Howells default: 21674983ac2SDavid Howells goto bad_value; 21774983ac2SDavid Howells } 21874983ac2SDavid Howells 21974983ac2SDavid Howells if (result->uint_32 > INT_MAX) 22074983ac2SDavid Howells goto bad_value; 22174983ac2SDavid Howells goto maybe_okay; 22231d921c7SDavid Howells } 22331d921c7SDavid Howells 22431d921c7SDavid Howells case fs_param_is_blockdev: 22531d921c7SDavid Howells case fs_param_is_path: 22631d921c7SDavid Howells goto okay; 22731d921c7SDavid Howells default: 22831d921c7SDavid Howells BUG(); 22931d921c7SDavid Howells } 23031d921c7SDavid Howells 23131d921c7SDavid Howells maybe_okay: 23231d921c7SDavid Howells if (ret < 0) 23331d921c7SDavid Howells goto bad_value; 23431d921c7SDavid Howells okay: 23531d921c7SDavid Howells return p->opt; 23631d921c7SDavid Howells 23731d921c7SDavid Howells bad_value: 23831d921c7SDavid Howells return invalf(fc, "%s: Bad value for '%s'", desc->name, param->key); 23931d921c7SDavid Howells unknown_parameter: 24031d921c7SDavid Howells return -ENOPARAM; 24131d921c7SDavid Howells } 24231d921c7SDavid Howells EXPORT_SYMBOL(fs_parse); 24331d921c7SDavid Howells 24431d921c7SDavid Howells /** 24531d921c7SDavid Howells * fs_lookup_param - Look up a path referred to by a parameter 24631d921c7SDavid Howells * @fc: The filesystem context to log errors through. 24731d921c7SDavid Howells * @param: The parameter. 24831d921c7SDavid Howells * @want_bdev: T if want a blockdev 24931d921c7SDavid Howells * @_path: The result of the lookup 25031d921c7SDavid Howells */ 25131d921c7SDavid Howells int fs_lookup_param(struct fs_context *fc, 25231d921c7SDavid Howells struct fs_parameter *param, 25331d921c7SDavid Howells bool want_bdev, 25431d921c7SDavid Howells struct path *_path) 25531d921c7SDavid Howells { 25631d921c7SDavid Howells struct filename *f; 25731d921c7SDavid Howells unsigned int flags = 0; 25831d921c7SDavid Howells bool put_f; 25931d921c7SDavid Howells int ret; 26031d921c7SDavid Howells 26131d921c7SDavid Howells switch (param->type) { 26231d921c7SDavid Howells case fs_value_is_string: 26331d921c7SDavid Howells f = getname_kernel(param->string); 26431d921c7SDavid Howells if (IS_ERR(f)) 26531d921c7SDavid Howells return PTR_ERR(f); 26631d921c7SDavid Howells put_f = true; 26731d921c7SDavid Howells break; 26831d921c7SDavid Howells case fs_value_is_filename_empty: 26931d921c7SDavid Howells flags = LOOKUP_EMPTY; 27031d921c7SDavid Howells /* Fall through */ 27131d921c7SDavid Howells case fs_value_is_filename: 27231d921c7SDavid Howells f = param->name; 27331d921c7SDavid Howells put_f = false; 27431d921c7SDavid Howells break; 27531d921c7SDavid Howells default: 27631d921c7SDavid Howells return invalf(fc, "%s: not usable as path", param->key); 27731d921c7SDavid Howells } 27831d921c7SDavid Howells 2797cdfa442SDavid Howells f->refcnt++; /* filename_lookup() drops our ref. */ 28031d921c7SDavid Howells ret = filename_lookup(param->dirfd, f, flags, _path, NULL); 28131d921c7SDavid Howells if (ret < 0) { 28231d921c7SDavid Howells errorf(fc, "%s: Lookup failure for '%s'", param->key, f->name); 28331d921c7SDavid Howells goto out; 28431d921c7SDavid Howells } 28531d921c7SDavid Howells 28631d921c7SDavid Howells if (want_bdev && 28731d921c7SDavid Howells !S_ISBLK(d_backing_inode(_path->dentry)->i_mode)) { 28831d921c7SDavid Howells path_put(_path); 28931d921c7SDavid Howells _path->dentry = NULL; 29031d921c7SDavid Howells _path->mnt = NULL; 29131d921c7SDavid Howells errorf(fc, "%s: Non-blockdev passed as '%s'", 29231d921c7SDavid Howells param->key, f->name); 29331d921c7SDavid Howells ret = -ENOTBLK; 29431d921c7SDavid Howells } 29531d921c7SDavid Howells 29631d921c7SDavid Howells out: 29731d921c7SDavid Howells if (put_f) 29831d921c7SDavid Howells putname(f); 29931d921c7SDavid Howells return ret; 30031d921c7SDavid Howells } 30131d921c7SDavid Howells EXPORT_SYMBOL(fs_lookup_param); 30231d921c7SDavid Howells 30331d921c7SDavid Howells #ifdef CONFIG_VALIDATE_FS_PARSER 30431d921c7SDavid Howells /** 30531d921c7SDavid Howells * validate_constant_table - Validate a constant table 30631d921c7SDavid Howells * @name: Name to use in reporting 30731d921c7SDavid Howells * @tbl: The constant table to validate. 30831d921c7SDavid Howells * @tbl_size: The size of the table. 30931d921c7SDavid Howells * @low: The lowest permissible value. 31031d921c7SDavid Howells * @high: The highest permissible value. 31131d921c7SDavid Howells * @special: One special permissible value outside of the range. 31231d921c7SDavid Howells */ 31331d921c7SDavid Howells bool validate_constant_table(const struct constant_table *tbl, size_t tbl_size, 31431d921c7SDavid Howells int low, int high, int special) 31531d921c7SDavid Howells { 31631d921c7SDavid Howells size_t i; 31731d921c7SDavid Howells bool good = true; 31831d921c7SDavid Howells 31931d921c7SDavid Howells if (tbl_size == 0) { 32031d921c7SDavid Howells pr_warn("VALIDATE C-TBL: Empty\n"); 32131d921c7SDavid Howells return true; 32231d921c7SDavid Howells } 32331d921c7SDavid Howells 32431d921c7SDavid Howells for (i = 0; i < tbl_size; i++) { 32531d921c7SDavid Howells if (!tbl[i].name) { 32631d921c7SDavid Howells pr_err("VALIDATE C-TBL[%zu]: Null\n", i); 32731d921c7SDavid Howells good = false; 32831d921c7SDavid Howells } else if (i > 0 && tbl[i - 1].name) { 32931d921c7SDavid Howells int c = strcmp(tbl[i-1].name, tbl[i].name); 33031d921c7SDavid Howells 33131d921c7SDavid Howells if (c == 0) { 33231d921c7SDavid Howells pr_err("VALIDATE C-TBL[%zu]: Duplicate %s\n", 33331d921c7SDavid Howells i, tbl[i].name); 33431d921c7SDavid Howells good = false; 33531d921c7SDavid Howells } 33631d921c7SDavid Howells if (c > 0) { 33731d921c7SDavid Howells pr_err("VALIDATE C-TBL[%zu]: Missorted %s>=%s\n", 33831d921c7SDavid Howells i, tbl[i-1].name, tbl[i].name); 33931d921c7SDavid Howells good = false; 34031d921c7SDavid Howells } 34131d921c7SDavid Howells } 34231d921c7SDavid Howells 34331d921c7SDavid Howells if (tbl[i].value != special && 34431d921c7SDavid Howells (tbl[i].value < low || tbl[i].value > high)) { 34531d921c7SDavid Howells pr_err("VALIDATE C-TBL[%zu]: %s->%d const out of range (%d-%d)\n", 34631d921c7SDavid Howells i, tbl[i].name, tbl[i].value, low, high); 34731d921c7SDavid Howells good = false; 34831d921c7SDavid Howells } 34931d921c7SDavid Howells } 35031d921c7SDavid Howells 35131d921c7SDavid Howells return good; 35231d921c7SDavid Howells } 35331d921c7SDavid Howells 35431d921c7SDavid Howells /** 35531d921c7SDavid Howells * fs_validate_description - Validate a parameter description 35631d921c7SDavid Howells * @desc: The parameter description to validate. 35731d921c7SDavid Howells */ 35831d921c7SDavid Howells bool fs_validate_description(const struct fs_parameter_description *desc) 35931d921c7SDavid Howells { 36031d921c7SDavid Howells const struct fs_parameter_spec *param, *p2; 36131d921c7SDavid Howells const char *name = desc->name; 3622710c957SAl Viro bool good = true; 36331d921c7SDavid Howells 36431d921c7SDavid Howells pr_notice("*** VALIDATE %s ***\n", name); 36531d921c7SDavid Howells 36631d921c7SDavid Howells if (!name[0]) { 36731d921c7SDavid Howells pr_err("VALIDATE Parser: No name\n"); 36831d921c7SDavid Howells name = "Unknown"; 36931d921c7SDavid Howells good = false; 37031d921c7SDavid Howells } 37131d921c7SDavid Howells 37231d921c7SDavid Howells if (desc->specs) { 37331d921c7SDavid Howells for (param = desc->specs; param->name; param++) { 37431d921c7SDavid Howells enum fs_parameter_type t = param->type; 37531d921c7SDavid Howells 37631d921c7SDavid Howells /* Check that the type is in range */ 37731d921c7SDavid Howells if (t == __fs_param_wasnt_defined || 37831d921c7SDavid Howells t >= nr__fs_parameter_type) { 37931d921c7SDavid Howells pr_err("VALIDATE %s: PARAM[%s] Bad type %u\n", 38031d921c7SDavid Howells name, param->name, t); 38131d921c7SDavid Howells good = false; 38231d921c7SDavid Howells } else if (t == fs_param_is_enum) { 383*5eede625SAl Viro const struct constant_table *e = param->data; 3842710c957SAl Viro if (!e || !e->name) { 3852710c957SAl Viro pr_err("VALIDATE %s: PARAM[%s] enum with no values\n", 3862710c957SAl Viro name, param->name); 3872710c957SAl Viro good = false; 3882710c957SAl Viro } 38931d921c7SDavid Howells } 39031d921c7SDavid Howells 39131d921c7SDavid Howells /* Check for duplicate parameter names */ 39231d921c7SDavid Howells for (p2 = desc->specs; p2 < param; p2++) { 39331d921c7SDavid Howells if (strcmp(param->name, p2->name) == 0) { 39431d921c7SDavid Howells pr_err("VALIDATE %s: PARAM[%s]: Duplicate\n", 39531d921c7SDavid Howells name, param->name); 39631d921c7SDavid Howells good = false; 39731d921c7SDavid Howells } 39831d921c7SDavid Howells } 39931d921c7SDavid Howells } 40031d921c7SDavid Howells } 40131d921c7SDavid Howells return good; 40231d921c7SDavid Howells } 40331d921c7SDavid Howells #endif /* CONFIG_VALIDATE_FS_PARSER */ 404