From 0b0e9162e26216da8f79f9e970c7c9ce38949b16 Mon Sep 17 00:00:00 2001 From: Andreas Dilger Date: Tue, 20 Feb 2024 17:59:25 -0700 Subject: [PATCH 1/1] LU-13791 mdt: allow using symbolic capability names Allow "mdt.*.enable_cap_mask" param set and print symbolic names, similar to the "debug" and "subsystem_debug" parameters. The allowed parameter names are in the capabilities(7) man page, in either upper or lowercase, like cap_chown, cap_dac_read_search, etc. along with "all" to enable all capabilities if clients are trusted. For example: lctl set_param -P mdt.lfs-*.enable_cap_mask=+cap_dac_read_search Since kernel_cap_t is a 64-bit value, enhance cfs_str2mask() to take u64 mask arguments. The calling libcfs_debug_str2mask() sticks with "int mask" for now. Split the core out from libcfs_debug_mask2str() into a new helper function cfs_mask2str() so it can be called directly. Fixes: 54f677651b ("LU-13791 mdt: parameter to tune capabilities") Signed-off-by: Andreas Dilger Change-Id: I3f71f61a17d4d3614e46a526c60e709d9eb825b3 Reviewed-on: https://review.whamcloud.com/c/fs/lustre-release/+/54118 Tested-by: jenkins Tested-by: Maloo Reviewed-by: Alexander Zarochentsev Reviewed-by: Sebastien Buisson Reviewed-by: Oleg Drokin --- libcfs/include/libcfs/libcfs_string.h | 4 +- libcfs/libcfs/debug.c | 70 ++++++++-------------------- libcfs/libcfs/libcfs_string.c | 57 +++++++++++++++++++++-- libcfs/libcfs/module.c | 2 +- lustre/mdd/mdd_device.c | 6 ++- lustre/mdd/mdd_lproc.c | 2 +- lustre/mdt/mdt_internal.h | 31 +++++++++++++ lustre/mdt/mdt_lproc.c | 87 ++++++++++++++++++++++++++--------- lustre/tests/sanity-sec.sh | 5 +- 9 files changed, 182 insertions(+), 82 deletions(-) diff --git a/libcfs/include/libcfs/libcfs_string.h b/libcfs/include/libcfs/libcfs_string.h index 5bbb8dd..54bac97 100644 --- a/libcfs/include/libcfs/libcfs_string.h +++ b/libcfs/include/libcfs/libcfs_string.h @@ -41,7 +41,9 @@ /* libcfs_string.c */ /* Convert a text string to a bitmask */ int cfs_str2mask(const char *str, const char *(*bit2str)(int bit), - int *oldmask, int minmask, int allmask, int defmask); + u64 *oldmask, u64 minmask, u64 allmask, u64 defmask); +int cfs_mask2str(char *str, int size, u64 mask, const char *(*bit2str)(int), + char sep); /* * Structure to represent \ token of the syntax. diff --git a/libcfs/libcfs/debug.c b/libcfs/libcfs/debug.c index 9cc4c6a..66ca9d7 100644 --- a/libcfs/libcfs/debug.c +++ b/libcfs/libcfs/debug.c @@ -283,78 +283,44 @@ static const char *libcfs_debug_dbg2str(int debug) return libcfs_debug_masks[debug]; } -int -libcfs_debug_mask2str(char *str, int size, int mask, int is_subsys) +int libcfs_debug_mask2str(char *str, int size, int mask, int is_subsys) { - const char *(*fn)(int bit) = is_subsys ? libcfs_debug_subsys2str : - libcfs_debug_dbg2str; - int len = 0; - const char *token; - int i; - - if (mask == 0) { /* "0" */ - if (size > 0) - str[0] = '0'; - len = 1; - } else { /* space-separated tokens */ - for (i = 0; i < 32; i++) { - if ((mask & BIT(i)) == 0) - continue; - - token = fn(i); - if (!token) /* unused bit */ - continue; - - if (len > 0) { /* separator? */ - if (len < size) - str[len] = ' '; - len++; - } - - while (*token != 0) { - if (len < size) - str[len] = *token; - token++; - len++; - } - } - } - - /* terminate 'str' */ - if (len < size) - str[len] = 0; - else - str[size - 1] = 0; + const char *(*bit2str)(int bit) = is_subsys ? libcfs_debug_subsys2str : + libcfs_debug_dbg2str; - return len; + return cfs_mask2str(str, size, mask, bit2str, ' '); } -int -libcfs_debug_str2mask(int *mask, const char *str, int is_subsys) +int libcfs_debug_str2mask(int *mask, const char *str, int is_subsys) { - const char *(*fn)(int bit) = is_subsys ? libcfs_debug_subsys2str : - libcfs_debug_dbg2str; + const char *(*bit2str)(int bit) = is_subsys ? libcfs_debug_subsys2str : + libcfs_debug_dbg2str; + u64 newmask = *mask; int m = 0; int matched; - int n; - int t; + int n, t; + int rc; /* Allow a number for backwards compatibility */ for (n = strlen(str); n > 0; n--) - if (!isspace(str[n-1])) + if (!isspace(str[n - 1])) break; matched = n; t = sscanf(str, "%i%n", &m, &matched); if (t >= 1 && matched == n) { /* don't print warning for lctl set_param debug=0 or -1 */ if (m != 0 && m != -1) - CWARN("You are trying to use a numerical value for the mask - this will be deprecated in a future release.\n"); + CWARN("using a numerical debug mask is deprecated\n"); *mask = m; return 0; } - return cfs_str2mask(str, fn, mask, is_subsys ? 0 : D_CANTMASK, ~0, - is_subsys ? LIBCFS_S_DEFAULT : LIBCFS_D_DEFAULT); + rc = cfs_str2mask(str, bit2str, &newmask, is_subsys ? 0 : D_CANTMASK, + ~0, is_subsys ? LIBCFS_S_DEFAULT : LIBCFS_D_DEFAULT); + + *mask = newmask; + + return rc; } char lnet_debug_log_upcall[1024] = "/usr/lib/lustre/lnet_debug_log_upcall"; diff --git a/libcfs/libcfs/libcfs_string.c b/libcfs/libcfs/libcfs_string.c index 0b1c621..cfa3c89 100644 --- a/libcfs/libcfs/libcfs_string.c +++ b/libcfs/libcfs/libcfs_string.c @@ -39,13 +39,61 @@ #include #include + +/* convert a binary mask to a string of bit names */ +int cfs_mask2str(char *str, int size, u64 mask, const char *(*bit2str)(int bit), + char sep) +{ + int len = 0; + const char *token; + int i; + + if (mask == 0) { /* "0" */ + if (size > 0) + str[0] = '0'; + len = 1; + } else { /* space-separated tokens */ + for (i = 0; i < 64; i++) { + if ((mask & BIT(i)) == 0) + continue; + + token = bit2str(i); + if (!token) /* unused bit */ + continue; + + if (len > 0) { /* separator? */ + if (len < size) + str[len] = sep; + len++; + } + + while (*token != 0) { + if (len < size) + str[len] = *token; + token++; + len++; + } + } + } + + /* terminate 'str' */ + if (len < size) + str[len++] = '\n'; + if (len < size) + str[len] = '\0'; + else + str[size - 1] = '\0'; + + return len; +} +EXPORT_SYMBOL(cfs_mask2str); + /* Convert a text string to a bitmask */ int cfs_str2mask(const char *str, const char *(*bit2str)(int bit), - int *oldmask, int minmask, int allmask, int defmask) + u64 *oldmask, u64 minmask, u64 allmask, u64 defmask) { const char *debugstr; - char op = 0; - int newmask = minmask, i, len, found = 0; + u64 newmask = minmask, found = 0; ENTRY; /* must be a list of tokens separated by whitespace or comma, @@ -55,6 +103,9 @@ int cfs_str2mask(const char *str, const char *(*bit2str)(int bit), * applies to all following tokens up to the next operator. */ while (*str != 0) { + int i, len; + char op = 0; + while (isspace(*str) || *str == ',') str++; if (*str == 0) diff --git a/libcfs/libcfs/module.c b/libcfs/libcfs/module.c index 4e0440b..e65c1c7 100644 --- a/libcfs/libcfs/module.c +++ b/libcfs/libcfs/module.c @@ -124,7 +124,7 @@ static int proc_dobitmasks(struct ctl_table *table, int write, rc = 0; } else { rc = cfs_trace_copyout_string(buffer, nob, - tmpstr + pos, "\n"); + tmpstr + pos, NULL); } } else { tmpstr = memdup_user_nul(buffer, nob); diff --git a/lustre/mdd/mdd_device.c b/lustre/mdd/mdd_device.c index d39fbef..9068a17 100644 --- a/lustre/mdd/mdd_device.c +++ b/lustre/mdd/mdd_device.c @@ -1798,13 +1798,15 @@ static int mdd_changelog_user_register(const struct lu_env *env, spin_unlock(&mdd->mdd_cl.mc_lock); if (mask) { + u64 newmask = CHANGELOG_DEFMASK; + /* if user will use relative mask apply it on default one */ - rec->cur_mask = CHANGELOG_DEFMASK; - rc = cfs_str2mask(mask, changelog_type2str, &rec->cur_mask, + rc = cfs_str2mask(mask, changelog_type2str, &newmask, CHANGELOG_MINMASK, CHANGELOG_ALLMASK, CHANGELOG_DEFMASK); if (rc) GOTO(out_users, rc); + rec->cur_mask = newmask; } else if (mdd->mdd_cl.mc_proc_mask == CHANGELOG_MINMASK) { /* a maskless users means default mask but only if server has * no specific mask set diff --git a/lustre/mdd/mdd_lproc.c b/lustre/mdd/mdd_lproc.c index a8746a6..427eb66 100644 --- a/lustre/mdd/mdd_lproc.c +++ b/lustre/mdd/mdd_lproc.c @@ -121,7 +121,7 @@ mdd_changelog_mask_seq_write(struct file *file, const char __user *buffer, char *kernbuf; int rc; int oldmask = mdd->mdd_cl.mc_proc_mask; - int newmask = oldmask; + u64 newmask = oldmask; ENTRY; diff --git a/lustre/mdt/mdt_internal.h b/lustre/mdt/mdt_internal.h index 631952d..34f748e 100644 --- a/lustre/mdt/mdt_internal.h +++ b/lustre/mdt/mdt_internal.h @@ -1479,6 +1479,37 @@ static inline bool mdt_changelog_allow(struct mdt_thread_info *info) return is_admin; } +/* convert a capability into an integer to print or manage more easily */ +static inline u64 mdt_cap2num(kernel_cap_t cap) +{ +#ifdef CAP_FOR_EACH_U32 + /* kernels before v6.2-13111-gf122a08b197d had a more complex + * kernel_cap_t structure with an array of __u32 values, but this + * was then fixed to have a single __u64 value. There are accessor + * functions for the old kernel_cap_t but since that is now dead code + * it isn't worthwhile to jump through hoops for compatibility for it. + */ + return ((u64)cap.cap[1] << 32) | cap.cap[0]; +#else + return cap.val; +#endif +} + +/* convert an integer into a capabilityt */ +static inline kernel_cap_t mdt_num2cap(u64 num) +{ + kernel_cap_t cap; + +#ifdef CAP_FOR_EACH_U32 + cap.cap[0] = num; + cap.cap[1] = (num >> 32); +#else + cap.val = num; +#endif + + return cap; +} + /* We forbid operations from encryption-unaware clients if they try to * manipulate encrypted files/directories. */ diff --git a/lustre/mdt/mdt_lproc.c b/lustre/mdt/mdt_lproc.c index f92e1e2..e848540 100644 --- a/lustre/mdt/mdt_lproc.c +++ b/lustre/mdt/mdt_lproc.c @@ -589,23 +589,64 @@ mdt_nosquash_nids_seq_write(struct file *file, const char __user *buffer, } LPROC_SEQ_FOPS(mdt_nosquash_nids); +static const char *mdt_cap2str(int cap) +{ + /* We don't allow using all capabilities, but the fields must exist. + * The supported capabilities are CAP_FS_SET and CAP_NFSD_SET, plus + * CAP_SYS_ADMIN for a bunch of HSM operations (that should be fixed). + */ + static const char *const capability_names[] = { + "cap_chown", /* 0 */ + "cap_dac_override", /* 1 */ + "cap_dac_read_search", /* 2 */ + "cap_fowner", /* 3 */ + "cap_fsetid", /* 4 */ + NULL, /* 5 */ + NULL, /* 6 */ + NULL, /* 7 */ + NULL, /* 8 */ + "cap_linux_immutable", /* 9 */ + NULL, /* 10 */ + NULL, /* 11 */ + NULL, /* 12 */ + NULL, /* 13 */ + NULL, /* 14 */ + NULL, /* 15 */ + NULL, /* 16 */ + NULL, /* 17 */ + NULL, /* 18 */ + NULL, /* 19 */ + NULL, /* 20 */ + /* we should use more precise capabilities than this */ + "cap_sys_admin", /* 21 */ + NULL, /* 22 */ + NULL, /* 23 */ + "cap_sys_resource", /* 24 */ + NULL, /* 25 */ + NULL, /* 26 */ + "cap_mknod", /* 27 */ + NULL, /* 28 */ + NULL, /* 29 */ + NULL, /* 30 */ + NULL, /* 31 */ + "cap_mac_override", /* 32 */ + }; + + if (cap >= ARRAY_SIZE(capability_names)) + return NULL; + + return capability_names[cap]; +} + static ssize_t enable_cap_mask_show(struct kobject *kobj, struct attribute *attr, char *buf) { struct obd_device *obd = container_of(kobj, struct obd_device, obd_kset.kobj); struct mdt_device *mdt = mdt_dev(obd->obd_lu_dev); - u64 cap; - - BUILD_BUG_ON(_KERNEL_CAP_T_SIZE != sizeof(u64)); + u64 mask = mdt_cap2num(mdt->mdt_enable_cap_mask); -#ifdef CAP_FOR_EACH_U32 /* kernels before v6.2-13111-gf122a08b197d */ - cap = ((u64)mdt->mdt_enable_cap_mask.cap[1] << 32) | - mdt->mdt_enable_cap_mask.cap[0]; -#else - cap = mdt->mdt_enable_cap_mask.val; -#endif - return scnprintf(buf, PAGE_SIZE, "%#0llx\n", cap); + return cfs_mask2str(buf, PAGE_SIZE, mask, mdt_cap2str, ','); } static ssize_t enable_cap_mask_store(struct kobject *kobj, @@ -615,24 +656,28 @@ static ssize_t enable_cap_mask_store(struct kobject *kobj, struct obd_device *obd = container_of(kobj, struct obd_device, obd_kset.kobj); struct mdt_device *mdt = mdt_dev(obd->obd_lu_dev); + static kernel_cap_t allowed_cap = CAP_EMPTY_SET; unsigned long long val; int rc; rc = kstrtoull(buffer, 0, &val); + if (rc == -EINVAL) { + u64 cap = mdt_cap2num(mdt->mdt_enable_cap_mask); + + /* the "allmask" is filtered by allowed_mask below */ + rc = cfs_str2mask(buffer, mdt_cap2str, &cap, 0, ~0ULL, 0); + val = cap; + } if (rc) - /* should also accept symbolic names via cfs_str2mask() */ return rc; -#ifdef CAP_FOR_EACH_U32 - mdt->mdt_enable_cap_mask.cap[0] = val & - (CAP_FS_MASK_B0 | CAP_TO_MASK(CAP_SYS_RESOURCE) | - CAP_TO_MASK(CAP_LINUX_IMMUTABLE)); - mdt->mdt_enable_cap_mask.cap[1] = (val >> 32) & CAP_FS_MASK_B1; -#else - mdt->mdt_enable_cap_mask.val = val & - (CAP_FS_MASK | BIT_ULL(CAP_SYS_RESOURCE) | - BIT_ULL(CAP_LINUX_IMMUTABLE)); -#endif + /* All of the capabilities that we currently allow/check */ + if (unlikely(cap_isclear(allowed_cap))) { + allowed_cap = CAP_FS_SET; + cap_raise(allowed_cap, CAP_SYS_RESOURCE); + } + + mdt->mdt_enable_cap_mask = cap_intersect(mdt_num2cap(val), allowed_cap); return count; } diff --git a/lustre/tests/sanity-sec.sh b/lustre/tests/sanity-sec.sh index ff65865..659a110 100755 --- a/lustre/tests/sanity-sec.sh +++ b/lustre/tests/sanity-sec.sh @@ -4391,7 +4391,10 @@ test_51() { old_cap=($(do_nodes $mdts $LCTL get_param -n $cap_param 2>/dev/null)) if [[ -n "$old_cap" ]]; then - do_nodes $mdts $LCTL set_param $cap_param=0xf + local new_cap="+cap_chown+cap_fowner+cap_dac_override+cap_dac_read_search" + (( $MDS1_VERSION > $(version_code 2.14.0.135) )) || new_cap=0xf + echo "old_cap: $old_cap new_cap: $new_cap" + do_nodes $mdts $LCTL set_param $cap_param=$new_cap stack_trap "do_nodes $mdts $LCTL set_param $cap_param=$old_cap" fi -- 1.8.3.1