diff options
Diffstat (limited to 'security')
57 files changed, 1685 insertions, 763 deletions
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 63ddefb6dd..2d9f2a4b45 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -226,7 +226,7 @@ static int __aafs_setup_d_inode(struct inode *dir, struct dentry *dentry, inode->i_ino = get_next_ino(); inode->i_mode = mode; - inode->i_atime = inode->i_mtime = inode_set_ctime_current(inode); + simple_inode_init_ts(inode); inode->i_private = data; if (S_ISDIR(mode)) { inode->i_op = iops ? iops : &simple_dir_inode_operations; @@ -619,23 +619,23 @@ static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms, if (profile_unconfined(profile)) return; - if (rules->file.dfa && *match_str == AA_CLASS_FILE) { - state = aa_dfa_match_len(rules->file.dfa, - rules->file.start[AA_CLASS_FILE], + if (rules->file->dfa && *match_str == AA_CLASS_FILE) { + state = aa_dfa_match_len(rules->file->dfa, + rules->file->start[AA_CLASS_FILE], match_str + 1, match_len - 1); if (state) { struct path_cond cond = { }; - tmp = *(aa_lookup_fperms(&(rules->file), state, &cond)); + tmp = *(aa_lookup_fperms(rules->file, state, &cond)); } - } else if (rules->policy.dfa) { + } else if (rules->policy->dfa) { if (!RULE_MEDIATES(rules, *match_str)) return; /* no change to current perms */ - state = aa_dfa_match_len(rules->policy.dfa, - rules->policy.start[0], + state = aa_dfa_match_len(rules->policy->dfa, + rules->policy->start[0], match_str, match_len); if (state) - tmp = *aa_lookup_perms(&rules->policy, state); + tmp = *aa_lookup_perms(rules->policy, state); } aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum_raw(perms, &tmp); @@ -1096,7 +1096,7 @@ static int seq_profile_attach_show(struct seq_file *seq, void *v) struct aa_profile *profile = labels_profile(label); if (profile->attach.xmatch_str) seq_printf(seq, "%s\n", profile->attach.xmatch_str); - else if (profile->attach.xmatch.dfa) + else if (profile->attach.xmatch->dfa) seq_puts(seq, "<unknown>\n"); else seq_printf(seq, "%s\n", profile->base.name); @@ -1315,7 +1315,6 @@ SEQ_RAWDATA_FOPS(compressed_size); static int decompress_zstd(char *src, size_t slen, char *dst, size_t dlen) { -#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY if (slen < dlen) { const size_t wksp_len = zstd_dctx_workspace_bound(); zstd_dctx *ctx; @@ -1342,7 +1341,6 @@ cleanup: kvfree(wksp); return ret; } -#endif if (dlen < slen) return -EINVAL; @@ -1558,7 +1556,8 @@ void __aafs_profile_migrate_dents(struct aa_profile *old, if (new->dents[i]) { struct inode *inode = d_inode(new->dents[i]); - inode->i_mtime = inode_set_ctime_current(inode); + inode_set_mtime_to_ts(inode, + inode_set_ctime_current(inode)); } old->dents[i] = NULL; } @@ -2341,10 +2340,16 @@ static struct aa_sfs_entry aa_sfs_entry_domain[] = { AA_SFS_FILE_BOOLEAN("post_nnp_subset", 1), AA_SFS_FILE_BOOLEAN("computed_longest_left", 1), AA_SFS_DIR("attach_conditions", aa_sfs_entry_attach), + AA_SFS_FILE_BOOLEAN("disconnected.path", 1), AA_SFS_FILE_STRING("version", "1.2"), { } }; +static struct aa_sfs_entry aa_sfs_entry_unconfined[] = { + AA_SFS_FILE_BOOLEAN("change_profile", 1), + { } +}; + static struct aa_sfs_entry aa_sfs_entry_versions[] = { AA_SFS_FILE_BOOLEAN("v5", 1), AA_SFS_FILE_BOOLEAN("v6", 1), @@ -2354,11 +2359,15 @@ static struct aa_sfs_entry aa_sfs_entry_versions[] = { { } }; +#define PERMS32STR "allow deny subtree cond kill complain prompt audit quiet hide xindex tag label" static struct aa_sfs_entry aa_sfs_entry_policy[] = { AA_SFS_DIR("versions", aa_sfs_entry_versions), AA_SFS_FILE_BOOLEAN("set_load", 1), /* number of out of band transitions supported */ AA_SFS_FILE_U64("outofband", MAX_OOB_SUPPORTED), + AA_SFS_FILE_U64("permstable32_version", 1), + AA_SFS_FILE_STRING("permstable32", PERMS32STR), + AA_SFS_DIR("unconfined_restrictions", aa_sfs_entry_unconfined), { } }; @@ -2371,6 +2380,7 @@ static struct aa_sfs_entry aa_sfs_entry_mount[] = { static struct aa_sfs_entry aa_sfs_entry_ns[] = { AA_SFS_FILE_BOOLEAN("profile", 1), AA_SFS_FILE_BOOLEAN("pivot_root", 0), + AA_SFS_FILE_STRING("mask", "userns_create"), { } }; @@ -2385,6 +2395,12 @@ static struct aa_sfs_entry aa_sfs_entry_query[] = { AA_SFS_DIR("label", aa_sfs_entry_query_label), { } }; + +static struct aa_sfs_entry aa_sfs_entry_io_uring[] = { + AA_SFS_FILE_STRING("mask", "sqpoll override_creds"), + { } +}; + static struct aa_sfs_entry aa_sfs_entry_features[] = { AA_SFS_DIR("policy", aa_sfs_entry_policy), AA_SFS_DIR("domain", aa_sfs_entry_domain), @@ -2398,6 +2414,7 @@ static struct aa_sfs_entry aa_sfs_entry_features[] = { AA_SFS_DIR("ptrace", aa_sfs_entry_ptrace), AA_SFS_DIR("signal", aa_sfs_entry_signal), AA_SFS_DIR("query", aa_sfs_entry_query), + AA_SFS_DIR("io_uring", aa_sfs_entry_io_uring), { } }; @@ -2547,7 +2564,7 @@ static int aa_mk_null_file(struct dentry *parent) inode->i_ino = get_next_ino(); inode->i_mode = S_IFCHR | S_IRUGO | S_IWUGO; - inode->i_atime = inode->i_mtime = inode_set_ctime_current(inode); + simple_inode_init_ts(inode); init_special_inode(inode, S_IFCHR | S_IRUGO | S_IWUGO, MKDEV(MEM_MAJOR, 3)); d_instantiate(dentry, inode); diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c index 6933cb2f67..45beb1c5f7 100644 --- a/security/apparmor/audit.c +++ b/security/apparmor/audit.c @@ -58,8 +58,8 @@ static const char *const aa_class_names[] = { "io_uring", "module", "lsm", - "unknown", - "unknown", + "namespace", + "io_uring", "unknown", "unknown", "unknown", diff --git a/security/apparmor/capability.c b/security/apparmor/capability.c index 2fb6a2ea0b..9934df16c8 100644 --- a/security/apparmor/capability.c +++ b/security/apparmor/capability.c @@ -38,8 +38,8 @@ static DEFINE_PER_CPU(struct audit_cache, audit_cache); /** * audit_cb - call back for capability components of audit struct - * @ab - audit buffer (NOT NULL) - * @va - audit struct to audit data from (NOT NULL) + * @ab: audit buffer (NOT NULL) + * @va: audit struct to audit data from (NOT NULL) */ static void audit_cb(struct audit_buffer *ab, void *va) { @@ -51,7 +51,7 @@ static void audit_cb(struct audit_buffer *ab, void *va) /** * audit_caps - audit a capability - * @as: audit data + * @ad: audit data * @profile: profile being tested for confinement (NOT NULL) * @cap: capability tested * @error: error code returned by test @@ -140,7 +140,7 @@ static int profile_capable(struct aa_profile *profile, int cap, /** * aa_capable - test permission to use capability - * @subj_cread: cred we are testing capability against + * @subj_cred: cred we are testing capability against * @label: label being tested for capability (NOT NULL) * @cap: capability to be tested * @opts: CAP_OPT_NOAUDIT bit determines whether audit record is generated diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 543105cf7e..89fbeab4b3 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -31,7 +31,7 @@ /** * may_change_ptraced_domain - check if can change profile on ptraced task - * @cred: cred of task changing domain + * @to_cred: cred of task changing domain * @to_label: profile to change to (NOT NULL) * @info: message if there is an error * @@ -77,7 +77,7 @@ out: /**** TODO: dedup to aa_label_match - needs perm and dfa, merging * specifically this is an exact copy of aa_label_match except * aa_compute_perms is replaced with aa_compute_fperms - * and policy.dfa with file.dfa + * and policy->dfa with file->dfa ****/ /* match a profile and its associated ns component if needed * Assumes visibility test has already been done. @@ -93,16 +93,16 @@ static inline aa_state_t match_component(struct aa_profile *profile, const char *ns_name; if (stack) - state = aa_dfa_match(rules->file.dfa, state, "&"); + state = aa_dfa_match(rules->file->dfa, state, "&"); if (profile->ns == tp->ns) - return aa_dfa_match(rules->file.dfa, state, tp->base.hname); + return aa_dfa_match(rules->file->dfa, state, tp->base.hname); /* try matching with namespace name and then profile */ ns_name = aa_ns_name(profile->ns, tp->ns, true); - state = aa_dfa_match_len(rules->file.dfa, state, ":", 1); - state = aa_dfa_match(rules->file.dfa, state, ns_name); - state = aa_dfa_match_len(rules->file.dfa, state, ":", 1); - return aa_dfa_match(rules->file.dfa, state, tp->base.hname); + state = aa_dfa_match_len(rules->file->dfa, state, ":", 1); + state = aa_dfa_match(rules->file->dfa, state, ns_name); + state = aa_dfa_match_len(rules->file->dfa, state, ":", 1); + return aa_dfa_match(rules->file->dfa, state, tp->base.hname); } /** @@ -150,12 +150,12 @@ next: label_for_each_cont(i, label, tp) { if (!aa_ns_visible(profile->ns, tp->ns, subns)) continue; - state = aa_dfa_match(rules->file.dfa, state, "//&"); + state = aa_dfa_match(rules->file->dfa, state, "//&"); state = match_component(profile, tp, false, state); if (!state) goto fail; } - *perms = *(aa_lookup_fperms(&(rules->file), state, &cond)); + *perms = *(aa_lookup_fperms(rules->file, state, &cond)); aa_apply_modes_to_perms(profile, perms); if ((perms->allow & request) != request) return -EACCES; @@ -210,7 +210,7 @@ static int label_components_match(struct aa_profile *profile, return 0; next: - tmp = *(aa_lookup_fperms(&(rules->file), state, &cond)); + tmp = *(aa_lookup_fperms(rules->file, state, &cond)); aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum(perms, &tmp); label_for_each_cont(i, label, tp) { @@ -219,7 +219,7 @@ next: state = match_component(profile, tp, stack, start); if (!state) goto fail; - tmp = *(aa_lookup_fperms(&(rules->file), state, &cond)); + tmp = *(aa_lookup_fperms(rules->file, state, &cond)); aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum(perms, &tmp); } @@ -272,6 +272,7 @@ static int label_match(struct aa_profile *profile, struct aa_label *label, * @stack: whether this is a stacking request * @request: requested perms * @start: state to start matching in + * @perms: Returns computed perms (NOT NULL) * * * Returns: permission set @@ -316,7 +317,7 @@ static int aa_xattrs_match(const struct linux_binprm *bprm, might_sleep(); /* transition from exec match to xattr set */ - state = aa_dfa_outofband_transition(attach->xmatch.dfa, state); + state = aa_dfa_outofband_transition(attach->xmatch->dfa, state); d = bprm->file->f_path.dentry; for (i = 0; i < attach->xattr_count; i++) { @@ -330,20 +331,20 @@ static int aa_xattrs_match(const struct linux_binprm *bprm, * that not present xattr can be distinguished from a 0 * length value or rule that matches any value */ - state = aa_dfa_null_transition(attach->xmatch.dfa, + state = aa_dfa_null_transition(attach->xmatch->dfa, state); /* Check xattr value */ - state = aa_dfa_match_len(attach->xmatch.dfa, state, + state = aa_dfa_match_len(attach->xmatch->dfa, state, value, size); - index = ACCEPT_TABLE(attach->xmatch.dfa)[state]; - perm = attach->xmatch.perms[index].allow; + index = ACCEPT_TABLE(attach->xmatch->dfa)[state]; + perm = attach->xmatch->perms[index].allow; if (!(perm & MAY_EXEC)) { ret = -EINVAL; goto out; } } /* transition to next element */ - state = aa_dfa_outofband_transition(attach->xmatch.dfa, state); + state = aa_dfa_outofband_transition(attach->xmatch->dfa, state); if (size < 0) { /* * No xattr match, so verify if transition to @@ -366,11 +367,11 @@ out: /** * find_attach - do attachment search for unconfined processes - * @bprm - binprm structure of transitioning task + * @bprm: binprm structure of transitioning task * @ns: the current namespace (NOT NULL) - * @head - profile list to walk (NOT NULL) - * @name - to match against (NOT NULL) - * @info - info message if there was an error (NOT NULL) + * @head: profile list to walk (NOT NULL) + * @name: to match against (NOT NULL) + * @info: info message if there was an error (NOT NULL) * * Do a linear search on the profiles in the list. There is a matching * preference where an exact match is preferred over a name which uses @@ -412,16 +413,16 @@ restart: * as another profile, signal a conflict and refuse to * match. */ - if (attach->xmatch.dfa) { + if (attach->xmatch->dfa) { unsigned int count; aa_state_t state; u32 index, perm; - state = aa_dfa_leftmatch(attach->xmatch.dfa, - attach->xmatch.start[AA_CLASS_XMATCH], + state = aa_dfa_leftmatch(attach->xmatch->dfa, + attach->xmatch->start[AA_CLASS_XMATCH], name, &count); - index = ACCEPT_TABLE(attach->xmatch.dfa)[state]; - perm = attach->xmatch.perms[index].allow; + index = ACCEPT_TABLE(attach->xmatch->dfa)[state]; + perm = attach->xmatch->perms[index].allow; /* any accepting state means a valid match. */ if (perm & MAY_EXEC) { int ret = 0; @@ -524,7 +525,7 @@ struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex, /* TODO: move lookup parsing to unpack time so this is a straight * index into the resultant label */ - for (*name = rules->file.trans.table[index]; !label && *name; + for (*name = rules->file->trans.table[index]; !label && *name; *name = next_name(xtype, *name)) { if (xindex & AA_X_CHILD) { struct aa_profile *new_profile; @@ -552,6 +553,7 @@ struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex, * @name: name to lookup (NOT NULL) * @xindex: index into x transition table * @lookupname: returns: name used in lookup if one was specified (NOT NULL) + * @info: info message if there was an error (NOT NULL) * * find label for a transition index * @@ -577,7 +579,7 @@ static struct aa_label *x_to_label(struct aa_profile *profile, break; case AA_X_TABLE: /* TODO: fix when perm mapping done at unload */ - stack = rules->file.trans.table[xindex & AA_X_INDEX_MASK]; + stack = rules->file->trans.table[xindex & AA_X_INDEX_MASK]; if (*stack != '&') { /* released by caller */ new = x_table_lookup(profile, xindex, lookupname); @@ -636,7 +638,7 @@ static struct aa_label *profile_transition(const struct cred *subj_cred, typeof(*rules), list); struct aa_label *new = NULL; const char *info = NULL, *name = NULL, *target = NULL; - aa_state_t state = rules->file.start[AA_CLASS_FILE]; + aa_state_t state = rules->file->start[AA_CLASS_FILE]; struct aa_perms perms = {}; bool nonewprivs = false; int error = 0; @@ -670,7 +672,7 @@ static struct aa_label *profile_transition(const struct cred *subj_cred, } /* find exec permissions for name */ - state = aa_str_perms(&(rules->file), state, name, cond, &perms); + state = aa_str_perms(rules->file, state, name, cond, &perms); if (perms.allow & MAY_EXEC) { /* exec permission determine how to transition */ new = x_to_label(profile, bprm, name, perms.xindex, &target, @@ -736,7 +738,7 @@ static int profile_onexec(const struct cred *subj_cred, { struct aa_ruleset *rules = list_first_entry(&profile->rules, typeof(*rules), list); - aa_state_t state = rules->file.start[AA_CLASS_FILE]; + aa_state_t state = rules->file->start[AA_CLASS_FILE]; struct aa_perms perms = {}; const char *xname = NULL, *info = "change_profile onexec"; int error = -EACCES; @@ -769,7 +771,7 @@ static int profile_onexec(const struct cred *subj_cred, } /* find exec permissions for name */ - state = aa_str_perms(&(rules->file), state, xname, cond, &perms); + state = aa_str_perms(rules->file, state, xname, cond, &perms); if (!(perms.allow & AA_MAY_ONEXEC)) { info = "no change_onexec valid for executable"; goto audit; @@ -778,7 +780,7 @@ static int profile_onexec(const struct cred *subj_cred, * onexec permission is linked to exec with a standard pairing * exec\0change_profile */ - state = aa_dfa_null_transition(rules->file.dfa, state); + state = aa_dfa_null_transition(rules->file->dfa, state); error = change_profile_perms(profile, onexec, stack, AA_MAY_ONEXEC, state, &perms); if (error) { @@ -1298,7 +1300,7 @@ static int change_profile_perms_wrapper(const char *op, const char *name, if (!error) error = change_profile_perms(profile, target, stack, request, - rules->file.start[AA_CLASS_FILE], + rules->file->start[AA_CLASS_FILE], perms); if (error) error = aa_audit_file(subj_cred, profile, perms, op, request, @@ -1309,6 +1311,8 @@ static int change_profile_perms_wrapper(const char *op, const char *name, return error; } +const char *stack_msg = "change_profile unprivileged unconfined converted to stacking"; + /** * aa_change_profile - perform a one-way profile transition * @fqname: name of profile may include namespace (NOT NULL) @@ -1368,6 +1372,28 @@ int aa_change_profile(const char *fqname, int flags) op = OP_CHANGE_PROFILE; } + /* This should move to a per profile test. Requires pushing build + * into callback + */ + if (!stack && unconfined(label) && + label == &labels_ns(label)->unconfined->label && + aa_unprivileged_unconfined_restricted && + /* TODO: refactor so this check is a fn */ + cap_capable(current_cred(), &init_user_ns, CAP_MAC_OVERRIDE, + CAP_OPT_NOAUDIT)) { + /* regardless of the request in this case apparmor + * stacks against unconfined so admin set policy can't be + * by-passed + */ + stack = true; + perms.audit = request; + (void) fn_for_each_in_ns(label, profile, + aa_audit_file(subj_cred, profile, &perms, op, + request, auditname, NULL, target, + GLOBAL_ROOT_UID, stack_msg, 0)); + perms.audit = 0; + } + if (*fqname == '&') { stack = true; /* don't have label_parse() do stacking */ @@ -1475,7 +1501,7 @@ check: } /* full transition will be built in exec path */ - error = aa_set_current_onexec(target, stack); + aa_set_current_onexec(target, stack); } audit: diff --git a/security/apparmor/file.c b/security/apparmor/file.c index 6fd21324a0..c03eb7c19f 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -182,7 +182,7 @@ static int path_name(const char *op, const struct cred *subj_cred, struct aa_perms default_perms = {}; /** * aa_lookup_fperms - convert dfa compressed perms to internal perms - * @dfa: dfa to lookup perms for (NOT NULL) + * @file_rules: the aa_policydb to lookup perms for (NOT NULL) * @state: state in dfa * @cond: conditions to consider (NOT NULL) * @@ -206,8 +206,8 @@ struct aa_perms *aa_lookup_fperms(struct aa_policydb *file_rules, /** * aa_str_perms - find permission that match @name - * @dfa: to match against (MAYBE NULL) - * @state: state to start matching in + * @file_rules: the aa_policydb to match against (NOT NULL) + * @start: state to start matching in * @name: string to match against dfa (NOT NULL) * @cond: conditions to consider for permission set computation (NOT NULL) * @perms: Returns - the permissions found when matching @name @@ -236,7 +236,7 @@ static int __aa_path_perm(const char *op, const struct cred *subj_cred, if (profile_unconfined(profile)) return 0; - aa_str_perms(&(rules->file), rules->file.start[AA_CLASS_FILE], + aa_str_perms(rules->file, rules->file->start[AA_CLASS_FILE], name, cond, perms); if (request & ~perms->allow) e = -EACCES; @@ -353,16 +353,16 @@ static int profile_path_link(const struct cred *subj_cred, error = -EACCES; /* aa_str_perms - handles the case of the dfa being NULL */ - state = aa_str_perms(&(rules->file), - rules->file.start[AA_CLASS_FILE], lname, + state = aa_str_perms(rules->file, + rules->file->start[AA_CLASS_FILE], lname, cond, &lperms); if (!(lperms.allow & AA_MAY_LINK)) goto audit; /* test to see if target can be paired with link */ - state = aa_dfa_null_transition(rules->file.dfa, state); - aa_str_perms(&(rules->file), state, tname, cond, &perms); + state = aa_dfa_null_transition(rules->file->dfa, state); + aa_str_perms(rules->file, state, tname, cond, &perms); /* force audit/quiet masks for link are stored in the second entry * in the link pair. @@ -384,7 +384,7 @@ static int profile_path_link(const struct cred *subj_cred, /* Do link perm subset test requiring allowed permission on link are * a subset of the allowed permissions on target. */ - aa_str_perms(&(rules->file), rules->file.start[AA_CLASS_FILE], + aa_str_perms(rules->file, rules->file->start[AA_CLASS_FILE], tname, cond, &perms); /* AA_MAY_LINK is not considered in the subset test */ diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h index 8a81557c9d..f83934913b 100644 --- a/security/apparmor/include/apparmor.h +++ b/security/apparmor/include/apparmor.h @@ -30,9 +30,10 @@ #define AA_CLASS_NET 14 #define AA_CLASS_LABEL 16 #define AA_CLASS_POSIX_MQUEUE 17 -#define AA_CLASS_IO_URING 18 #define AA_CLASS_MODULE 19 #define AA_CLASS_DISPLAY_LSM 20 +#define AA_CLASS_NS 21 +#define AA_CLASS_IO_URING 22 #define AA_CLASS_X 31 #define AA_CLASS_DBUS 32 diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index 42d701fec5..acbb03b9bd 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -103,6 +103,10 @@ enum audit_type { #define OP_PROF_LOAD "profile_load" #define OP_PROF_RM "profile_remove" +#define OP_USERNS_CREATE "userns_create" + +#define OP_URING_OVERRIDE "uring_override" +#define OP_URING_SQPOLL "uring_sqpoll" struct apparmor_audit_data { int error; @@ -152,6 +156,9 @@ struct apparmor_audit_data { const char *data; unsigned long flags; } mnt; + struct { + struct aa_label *target; + } uring; }; struct common_audit_data common; diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index 64dc6d1a7a..6e8f2aa66c 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -45,43 +45,6 @@ struct aa_file_ctx { u32 allow; }; -/** - * aa_alloc_file_ctx - allocate file_ctx - * @label: initial label of task creating the file - * @gfp: gfp flags for allocation - * - * Returns: file_ctx or NULL on failure - */ -static inline struct aa_file_ctx *aa_alloc_file_ctx(struct aa_label *label, - gfp_t gfp) -{ - struct aa_file_ctx *ctx; - - ctx = kzalloc(sizeof(struct aa_file_ctx), gfp); - if (ctx) { - spin_lock_init(&ctx->lock); - rcu_assign_pointer(ctx->label, aa_get_label(label)); - } - return ctx; -} - -/** - * aa_free_file_ctx - free a file_ctx - * @ctx: file_ctx to free (MAYBE_NULL) - */ -static inline void aa_free_file_ctx(struct aa_file_ctx *ctx) -{ - if (ctx) { - aa_put_label(rcu_access_pointer(ctx->label)); - kfree_sensitive(ctx); - } -} - -static inline struct aa_label *aa_get_file_label(struct aa_file_ctx *ctx) -{ - return aa_get_label_rcu(&ctx->label); -} - /* * The xindex is broken into 3 parts * - index - an index into either the exec name table or the variable table diff --git a/security/apparmor/include/lib.h b/security/apparmor/include/lib.h index 73c8a32c68..d7a894b103 100644 --- a/security/apparmor/include/lib.h +++ b/security/apparmor/include/lib.h @@ -16,6 +16,8 @@ #include "match.h" +extern struct aa_dfa *stacksplitdfa; + /* * DEBUG remains global (no per profile flag) since it is mostly used in sysctl * which is not related to profile accesses. diff --git a/security/apparmor/include/match.h b/security/apparmor/include/match.h index 58fbf67139..4bb0405c91 100644 --- a/security/apparmor/include/match.h +++ b/security/apparmor/include/match.h @@ -102,9 +102,6 @@ struct aa_dfa { struct table_header *tables[YYTD_ID_TSIZE]; }; -extern struct aa_dfa *nulldfa; -extern struct aa_dfa *stacksplitdfa; - #define byte_to_byte(X) (X) #define UNPACK_ARRAY(TABLE, BLOB, LEN, TTYPE, BTYPE, NTOHX) \ @@ -122,9 +119,6 @@ static inline size_t table_size(size_t len, size_t el_size) return ALIGN(sizeof(struct table_header) + len * el_size, 8); } -int aa_setup_dfa_engine(void); -void aa_teardown_dfa_engine(void); - #define aa_state_t unsigned int struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags); diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h index aa8515af67..67bf888c3b 100644 --- a/security/apparmor/include/net.h +++ b/security/apparmor/include/net.h @@ -52,7 +52,11 @@ struct aa_sk_ctx { }; #define SK_CTX(X) ((X)->sk_security) -#define SOCK_ctx(X) SOCK_INODE(X)->i_security +static inline struct aa_sk_ctx *aa_sock(const struct sock *sk) +{ + return sk->sk_security; +} + #define DEFINE_AUDIT_NET(NAME, OP, SK, F, T, P) \ struct lsm_network_audit NAME ## _net = { .sk = (SK), \ .family = (F)}; \ diff --git a/security/apparmor/include/perms.h b/security/apparmor/include/perms.h index 83534df893..0f7e913c3f 100644 --- a/security/apparmor/include/perms.h +++ b/security/apparmor/include/perms.h @@ -48,6 +48,9 @@ #define AA_LINK_SUBSET AA_MAY_LOCK /* overlaid */ +#define AA_MAY_CREATE_SQPOLL AA_MAY_CREATE +#define AA_MAY_OVERRIDE_CRED AA_MAY_APPEND +#define AA_URING_PERM_MASK (AA_MAY_OVERRIDE_CRED | AA_MAY_CREATE_SQPOLL) #define PERMS_CHRS_MASK (MAY_READ | MAY_WRITE | AA_MAY_CREATE | \ AA_MAY_DELETE | AA_MAY_LINK | AA_MAY_LOCK | \ diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index fa15a5c7fe..75088cc310 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -34,6 +34,7 @@ struct aa_ns; extern int unprivileged_userns_apparmor_policy; +extern int aa_unprivileged_unconfined_restricted; extern const char *const aa_profile_mode_names[]; #define APPARMOR_MODE_NAMES_MAX_INDEX 4 @@ -74,12 +75,14 @@ enum profile_mode { /* struct aa_policydb - match engine for a policy + * count: refcount for the pdb * dfa: dfa pattern match * perms: table of permissions * strs: table of strings, index by x * start: set of start states for the different classes of data */ struct aa_policydb { + struct kref count; struct aa_dfa *dfa; struct { struct aa_perms *perms; @@ -89,13 +92,36 @@ struct aa_policydb { aa_state_t start[AA_CLASS_LAST + 1]; }; -static inline void aa_destroy_policydb(struct aa_policydb *policy) +extern struct aa_policydb *nullpdb; + +struct aa_policydb *aa_alloc_pdb(gfp_t gfp); +void aa_pdb_free_kref(struct kref *kref); + +/** + * aa_get_pdb - increment refcount on @pdb + * @pdb: policydb (MAYBE NULL) + * + * Returns: pointer to @pdb if @pdb is NULL will return NULL + * Requires: @pdb must be held with valid refcount when called + */ +static inline struct aa_policydb *aa_get_pdb(struct aa_policydb *pdb) { - aa_put_dfa(policy->dfa); - if (policy->perms) - kvfree(policy->perms); - aa_free_str_table(&policy->trans); + if (pdb) + kref_get(&(pdb->count)); + + return pdb; +} +/** + * aa_put_pdb - put a pdb refcount + * @pdb: pdb to put refcount (MAYBE NULL) + * + * Requires: if @pdb != NULL that a valid refcount be held + */ +static inline void aa_put_pdb(struct aa_policydb *pdb) +{ + if (pdb) + kref_put(&pdb->count, aa_pdb_free_kref); } static inline struct aa_perms *aa_lookup_perms(struct aa_policydb *policy, @@ -139,8 +165,8 @@ struct aa_ruleset { int size; /* TODO: merge policy and file */ - struct aa_policydb policy; - struct aa_policydb file; + struct aa_policydb *policy; + struct aa_policydb *file; struct aa_caps caps; struct aa_rlimit rlimits; @@ -159,7 +185,7 @@ struct aa_ruleset { */ struct aa_attachment { const char *xmatch_str; - struct aa_policydb xmatch; + struct aa_policydb *xmatch; unsigned int xmatch_len; int xattr_count; char **xattrs; @@ -227,10 +253,6 @@ extern enum profile_mode aa_g_profile_mode; #define profiles_ns(P) ((P)->ns) #define name_is_shared(A, B) ((A)->hname && (A)->hname == (B)->hname) -void aa_add_profile(struct aa_policy *common, struct aa_profile *profile); - - -void aa_free_proxy_kref(struct kref *kref); struct aa_ruleset *aa_alloc_ruleset(gfp_t gfp); struct aa_profile *aa_alloc_profile(const char *name, struct aa_proxy *proxy, gfp_t gfp); @@ -239,14 +261,12 @@ struct aa_profile *aa_alloc_null(struct aa_profile *parent, const char *name, struct aa_profile *aa_new_learning_profile(struct aa_profile *parent, bool hat, const char *base, gfp_t gfp); void aa_free_profile(struct aa_profile *profile); -void aa_free_profile_kref(struct kref *kref); struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name); struct aa_profile *aa_lookupn_profile(struct aa_ns *ns, const char *hname, size_t n); struct aa_profile *aa_lookup_profile(struct aa_ns *ns, const char *name); struct aa_profile *aa_fqlookupn_profile(struct aa_label *base, const char *fqname, size_t n); -struct aa_profile *aa_match_profile(struct aa_ns *ns, const char *name); ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_label *label, u32 mask, struct aa_loaddata *udata); @@ -254,9 +274,6 @@ ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_label *label, char *name, size_t size); void __aa_profile_list_release(struct list_head *head); -#define PROF_ADD 1 -#define PROF_REPLACE 0 - #define profile_unconfined(X) ((X)->mode == APPARMOR_UNCONFINED) /** @@ -276,10 +293,10 @@ static inline aa_state_t RULE_MEDIATES(struct aa_ruleset *rules, unsigned char class) { if (class <= AA_CLASS_LAST) - return rules->policy.start[class]; + return rules->policy->start[class]; else - return aa_dfa_match_len(rules->policy.dfa, - rules->policy.start[0], &class, 1); + return aa_dfa_match_len(rules->policy->dfa, + rules->policy->start[0], &class, 1); } static inline aa_state_t RULE_MEDIATES_AF(struct aa_ruleset *rules, u16 AF) @@ -289,7 +306,7 @@ static inline aa_state_t RULE_MEDIATES_AF(struct aa_ruleset *rules, u16 AF) if (!state) return DFA_NOMATCH; - return aa_dfa_match_len(rules->policy.dfa, state, (char *) &be_af, 2); + return aa_dfa_match_len(rules->policy->dfa, state, (char *) &be_af, 2); } static inline aa_state_t ANY_RULE_MEDIATES(struct list_head *head, diff --git a/security/apparmor/include/policy_ns.h b/security/apparmor/include/policy_ns.h index 33d665516f..d646070fd9 100644 --- a/security/apparmor/include/policy_ns.h +++ b/security/apparmor/include/policy_ns.h @@ -86,10 +86,7 @@ const char *aa_ns_name(struct aa_ns *parent, struct aa_ns *child, bool subns); void aa_free_ns(struct aa_ns *ns); int aa_alloc_root_ns(void); void aa_free_root_ns(void); -void aa_free_ns_kref(struct kref *kref); -struct aa_ns *aa_find_ns(struct aa_ns *root, const char *name); -struct aa_ns *aa_findn_ns(struct aa_ns *root, const char *name, size_t n); struct aa_ns *__aa_lookupn_ns(struct aa_ns *view, const char *hname, size_t n); struct aa_ns *aa_lookupn_ns(struct aa_ns *view, const char *name, size_t n); struct aa_ns *__aa_find_or_create_ns(struct aa_ns *parent, const char *name, @@ -151,15 +148,4 @@ static inline struct aa_ns *__aa_find_ns(struct list_head *head, return __aa_findn_ns(head, name, strlen(name)); } -static inline struct aa_ns *__aa_lookup_ns(struct aa_ns *base, - const char *hname) -{ - return __aa_lookupn_ns(base, hname, strlen(hname)); -} - -static inline struct aa_ns *aa_lookup_ns(struct aa_ns *view, const char *name) -{ - return aa_lookupn_ns(view, name, strlen(name)); -} - #endif /* AA_NAMESPACE_H */ diff --git a/security/apparmor/include/task.h b/security/apparmor/include/task.h index 29ba55107b..b1aaaf60fa 100644 --- a/security/apparmor/include/task.h +++ b/security/apparmor/include/task.h @@ -30,7 +30,7 @@ struct aa_task_ctx { }; int aa_replace_current_label(struct aa_label *label); -int aa_set_current_onexec(struct aa_label *label, bool stack); +void aa_set_current_onexec(struct aa_label *label, bool stack); int aa_set_current_hat(struct aa_label *label, u64 token); int aa_restore_previous_label(u64 cookie); struct aa_label *aa_get_task_label(struct task_struct *task); @@ -96,4 +96,10 @@ int aa_may_ptrace(const struct cred *tracer_cred, struct aa_label *tracer, u32 request); + +#define AA_USERNS_CREATE 8 + +int aa_profile_ns_perm(struct aa_profile *profile, + struct apparmor_audit_data *ad, u32 request); + #endif /* __AA_TASK_H */ diff --git a/security/apparmor/ipc.c b/security/apparmor/ipc.c index c0d0dbd7b4..0cdf4340b0 100644 --- a/security/apparmor/ipc.c +++ b/security/apparmor/ipc.c @@ -92,8 +92,8 @@ static int profile_signal_perm(const struct cred *cred, ad->subj_cred = cred; ad->peer = peer; /* TODO: secondary cache check <profile, profile, perm> */ - state = aa_dfa_next(rules->policy.dfa, - rules->policy.start[AA_CLASS_SIGNAL], + state = aa_dfa_next(rules->policy->dfa, + rules->policy->start[AA_CLASS_SIGNAL], ad->signal); aa_label_match(profile, rules, peer, state, false, request, &perms); aa_apply_modes_to_perms(profile, &perms); diff --git a/security/apparmor/label.c b/security/apparmor/label.c index 8a2af96f4d..c71e4615dd 100644 --- a/security/apparmor/label.c +++ b/security/apparmor/label.c @@ -154,13 +154,14 @@ static int profile_cmp(struct aa_profile *a, struct aa_profile *b) /** * vec_cmp - label comparison for set ordering - * @a: label to compare (NOT NULL) - * @vec: vector of profiles to compare (NOT NULL) - * @n: length of @vec - * - * Returns: <0 if a < vec - * ==0 if a == vec - * >0 if a > vec + * @a: aa_profile to compare (NOT NULL) + * @an: length of @a + * @b: aa_profile to compare (NOT NULL) + * @bn: length of @b + * + * Returns: <0 if @a < @b + * ==0 if @a == @b + * >0 if @a > @b */ static int vec_cmp(struct aa_profile **a, int an, struct aa_profile **b, int bn) { @@ -256,6 +257,7 @@ static inline int unique(struct aa_profile **vec, int n) * aa_vec_unique - canonical sort and unique a list of profiles * @n: number of refcounted profiles in the list (@n > 0) * @vec: list of profiles to sort and merge + * @flags: null terminator flags of @vec * * Returns: the number of duplicates eliminated == references put * @@ -584,7 +586,7 @@ bool aa_label_is_unconfined_subset(struct aa_label *set, struct aa_label *sub) /** * __label_remove - remove @label from the label set - * @l: label to remove + * @label: label to remove * @new: label to redirect to * * Requires: labels_set(@label)->lock write_lock @@ -917,8 +919,8 @@ struct aa_label *aa_label_find(struct aa_label *label) /** * aa_label_insert - insert label @label into @ls or return existing label - * @ls - labelset to insert @label into - * @label - label to insert + * @ls: labelset to insert @label into + * @label: label to insert * * Requires: caller to hold a valid ref on @label * @@ -1204,7 +1206,6 @@ struct aa_label *aa_label_find_merge(struct aa_label *a, struct aa_label *b) /** * aa_label_merge - attempt to insert new merged label of @a and @b - * @ls: set of labels to insert label into (NOT NULL) * @a: label to merge with @b (NOT NULL) * @b: label to merge with @a (NOT NULL) * @gfp: memory allocation type @@ -1269,21 +1270,22 @@ static inline aa_state_t match_component(struct aa_profile *profile, const char *ns_name; if (profile->ns == tp->ns) - return aa_dfa_match(rules->policy.dfa, state, tp->base.hname); + return aa_dfa_match(rules->policy->dfa, state, tp->base.hname); /* try matching with namespace name and then profile */ ns_name = aa_ns_name(profile->ns, tp->ns, true); - state = aa_dfa_match_len(rules->policy.dfa, state, ":", 1); - state = aa_dfa_match(rules->policy.dfa, state, ns_name); - state = aa_dfa_match_len(rules->policy.dfa, state, ":", 1); - return aa_dfa_match(rules->policy.dfa, state, tp->base.hname); + state = aa_dfa_match_len(rules->policy->dfa, state, ":", 1); + state = aa_dfa_match(rules->policy->dfa, state, ns_name); + state = aa_dfa_match_len(rules->policy->dfa, state, ":", 1); + return aa_dfa_match(rules->policy->dfa, state, tp->base.hname); } /** * label_compound_match - find perms for full compound label * @profile: profile to find perms for + * @rules: ruleset to search * @label: label to check access permissions for - * @start: state to start match in + * @state: state to start match in * @subns: whether to do permission checks on components in a subns * @request: permissions to request * @perms: perms struct to set @@ -1321,12 +1323,12 @@ next: label_for_each_cont(i, label, tp) { if (!aa_ns_visible(profile->ns, tp->ns, subns)) continue; - state = aa_dfa_match(rules->policy.dfa, state, "//&"); + state = aa_dfa_match(rules->policy->dfa, state, "//&"); state = match_component(profile, rules, tp, state); if (!state) goto fail; } - *perms = *aa_lookup_perms(&rules->policy, state); + *perms = *aa_lookup_perms(rules->policy, state); aa_apply_modes_to_perms(profile, perms); if ((perms->allow & request) != request) return -EACCES; @@ -1379,7 +1381,7 @@ static int label_components_match(struct aa_profile *profile, return 0; next: - tmp = *aa_lookup_perms(&rules->policy, state); + tmp = *aa_lookup_perms(rules->policy, state); aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum(perms, &tmp); label_for_each_cont(i, label, tp) { @@ -1388,7 +1390,7 @@ next: state = match_component(profile, rules, tp, start); if (!state) goto fail; - tmp = *aa_lookup_perms(&rules->policy, state); + tmp = *aa_lookup_perms(rules->policy, state); aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum(perms, &tmp); } @@ -2037,7 +2039,7 @@ out: /** * __label_update - insert updated version of @label into labelset - * @label - the label to update/replace + * @label: the label to update/replace * * Returns: new label that is up to date * else NULL on failure diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index 7182a8b821..cd569fbbfe 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -342,8 +342,8 @@ void aa_profile_match_label(struct aa_profile *profile, /* TODO: doesn't yet handle extended types */ aa_state_t state; - state = aa_dfa_next(rules->policy.dfa, - rules->policy.start[AA_CLASS_LABEL], + state = aa_dfa_next(rules->policy->dfa, + rules->policy->start[AA_CLASS_LABEL], type); aa_label_match(profile, rules, label, state, false, request, perms); } diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 366cdfd6a7..608a849a74 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -49,12 +49,19 @@ union aa_buffer { DECLARE_FLEX_ARRAY(char, buffer); }; +struct aa_local_cache { + unsigned int hold; + unsigned int count; + struct list_head head; +}; + #define RESERVE_COUNT 2 static int reserve_count = RESERVE_COUNT; static int buffer_count; static LIST_HEAD(aa_global_buffers); static DEFINE_SPINLOCK(aa_buffers_lock); +static DEFINE_PER_CPU(struct aa_local_cache, aa_local_buffers); /* * LSM hook functions @@ -582,6 +589,114 @@ static int apparmor_file_mprotect(struct vm_area_struct *vma, false); } +#ifdef CONFIG_IO_URING +static const char *audit_uring_mask(u32 mask) +{ + if (mask & AA_MAY_CREATE_SQPOLL) + return "sqpoll"; + if (mask & AA_MAY_OVERRIDE_CRED) + return "override_creds"; + return ""; +} + +static void audit_uring_cb(struct audit_buffer *ab, void *va) +{ + struct apparmor_audit_data *ad = aad_of_va(va); + + if (ad->request & AA_URING_PERM_MASK) { + audit_log_format(ab, " requested=\"%s\"", + audit_uring_mask(ad->request)); + if (ad->denied & AA_URING_PERM_MASK) { + audit_log_format(ab, " denied=\"%s\"", + audit_uring_mask(ad->denied)); + } + } + if (ad->uring.target) { + audit_log_format(ab, " tcontext="); + aa_label_xaudit(ab, labels_ns(ad->subj_label), + ad->uring.target, + FLAGS_NONE, GFP_ATOMIC); + } +} + +static int profile_uring(struct aa_profile *profile, u32 request, + struct aa_label *new, int cap, + struct apparmor_audit_data *ad) +{ + unsigned int state; + struct aa_ruleset *rules; + int error = 0; + + AA_BUG(!profile); + + rules = list_first_entry(&profile->rules, typeof(*rules), list); + state = RULE_MEDIATES(rules, AA_CLASS_IO_URING); + if (state) { + struct aa_perms perms = { }; + + if (new) { + aa_label_match(profile, rules, new, state, + false, request, &perms); + } else { + perms = *aa_lookup_perms(rules->policy, state); + } + aa_apply_modes_to_perms(profile, &perms); + error = aa_check_perms(profile, &perms, request, ad, + audit_uring_cb); + } + + return error; +} + +/** + * apparmor_uring_override_creds - check the requested cred override + * @new: the target creds + * + * Check to see if the current task is allowed to override it's credentials + * to service an io_uring operation. + */ +static int apparmor_uring_override_creds(const struct cred *new) +{ + struct aa_profile *profile; + struct aa_label *label; + int error; + DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_IO_URING, + OP_URING_OVERRIDE); + + ad.uring.target = cred_label(new); + label = __begin_current_label_crit_section(); + error = fn_for_each(label, profile, + profile_uring(profile, AA_MAY_OVERRIDE_CRED, + cred_label(new), CAP_SYS_ADMIN, &ad)); + __end_current_label_crit_section(label); + + return error; +} + +/** + * apparmor_uring_sqpoll - check if a io_uring polling thread can be created + * + * Check to see if the current task is allowed to create a new io_uring + * kernel polling thread. + */ +static int apparmor_uring_sqpoll(void) +{ + struct aa_profile *profile; + struct aa_label *label; + int error; + DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_IO_URING, + OP_URING_SQPOLL); + + label = __begin_current_label_crit_section(); + error = fn_for_each(label, profile, + profile_uring(profile, AA_MAY_CREATE_SQPOLL, + NULL, CAP_SYS_ADMIN, &ad)); + __end_current_label_crit_section(label); + + return error; +} +#endif /* CONFIG_IO_URING */ + static int apparmor_sb_mount(const char *dev_name, const struct path *path, const char *type, unsigned long flags, void *data) { @@ -765,7 +880,7 @@ fail: * apparmor_bprm_committing_creds - do task cleanup on committing new creds * @bprm: binprm for the exec (NOT NULL) */ -static void apparmor_bprm_committing_creds(struct linux_binprm *bprm) +static void apparmor_bprm_committing_creds(const struct linux_binprm *bprm) { struct aa_label *label = aa_current_raw_label(); struct aa_label *new_label = cred_label(bprm->cred); @@ -787,7 +902,7 @@ static void apparmor_bprm_committing_creds(struct linux_binprm *bprm) * apparmor_bprm_committed_creds() - do cleanup after new creds committed * @bprm: binprm for the exec (NOT NULL) */ -static void apparmor_bprm_committed_creds(struct linux_binprm *bprm) +static void apparmor_bprm_committed_creds(const struct linux_binprm *bprm) { /* clear out temporary/transitional state from the context */ aa_clear_task_ctx_trans(task_ctx(current)); @@ -797,9 +912,9 @@ static void apparmor_bprm_committed_creds(struct linux_binprm *bprm) static void apparmor_current_getsecid_subj(u32 *secid) { - struct aa_label *label = aa_get_current_label(); + struct aa_label *label = __begin_current_label_crit_section(); *secid = label->secid; - aa_put_label(label); + __end_current_label_crit_section(label); } static void apparmor_task_getsecid_obj(struct task_struct *p, u32 *secid) @@ -850,6 +965,27 @@ static int apparmor_task_kill(struct task_struct *target, struct kernel_siginfo return error; } +static int apparmor_userns_create(const struct cred *cred) +{ + struct aa_label *label; + struct aa_profile *profile; + int error = 0; + DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_TASK, AA_CLASS_NS, + OP_USERNS_CREATE); + + ad.subj_cred = current_cred(); + + label = begin_current_label_crit_section(); + if (!unconfined(label)) { + error = fn_for_each(label, profile, + aa_profile_ns_perm(profile, &ad, + AA_USERNS_CREATE)); + } + end_current_label_crit_section(label); + + return error; +} + /** * apparmor_sk_alloc_security - allocate and attach the sk_security field */ @@ -861,7 +997,7 @@ static int apparmor_sk_alloc_security(struct sock *sk, int family, gfp_t flags) if (!ctx) return -ENOMEM; - SK_CTX(sk) = ctx; + sk->sk_security = ctx; return 0; } @@ -871,9 +1007,9 @@ static int apparmor_sk_alloc_security(struct sock *sk, int family, gfp_t flags) */ static void apparmor_sk_free_security(struct sock *sk) { - struct aa_sk_ctx *ctx = SK_CTX(sk); + struct aa_sk_ctx *ctx = aa_sock(sk); - SK_CTX(sk) = NULL; + sk->sk_security = NULL; aa_put_label(ctx->label); aa_put_label(ctx->peer); kfree(ctx); @@ -885,8 +1021,8 @@ static void apparmor_sk_free_security(struct sock *sk) static void apparmor_sk_clone_security(const struct sock *sk, struct sock *newsk) { - struct aa_sk_ctx *ctx = SK_CTX(sk); - struct aa_sk_ctx *new = SK_CTX(newsk); + struct aa_sk_ctx *ctx = aa_sock(sk); + struct aa_sk_ctx *new = aa_sock(newsk); if (new->label) aa_put_label(new->label); @@ -940,7 +1076,7 @@ static int apparmor_socket_post_create(struct socket *sock, int family, label = aa_get_current_label(); if (sock->sk) { - struct aa_sk_ctx *ctx = SK_CTX(sock->sk); + struct aa_sk_ctx *ctx = aa_sock(sock->sk); aa_put_label(ctx->label); ctx->label = aa_get_label(label); @@ -1125,7 +1261,7 @@ static int apparmor_socket_shutdown(struct socket *sock, int how) */ static int apparmor_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) { - struct aa_sk_ctx *ctx = SK_CTX(sk); + struct aa_sk_ctx *ctx = aa_sock(sk); if (!skb->secmark) return 0; @@ -1138,7 +1274,7 @@ static int apparmor_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) static struct aa_label *sk_peer_label(struct sock *sk) { - struct aa_sk_ctx *ctx = SK_CTX(sk); + struct aa_sk_ctx *ctx = aa_sock(sk); if (ctx->peer) return ctx->peer; @@ -1219,7 +1355,7 @@ static int apparmor_socket_getpeersec_dgram(struct socket *sock, */ static void apparmor_sock_graft(struct sock *sk, struct socket *parent) { - struct aa_sk_ctx *ctx = SK_CTX(sk); + struct aa_sk_ctx *ctx = aa_sock(sk); if (!ctx->label) ctx->label = aa_get_current_label(); @@ -1229,7 +1365,7 @@ static void apparmor_sock_graft(struct sock *sk, struct socket *parent) static int apparmor_inet_conn_request(const struct sock *sk, struct sk_buff *skb, struct request_sock *req) { - struct aa_sk_ctx *ctx = SK_CTX(sk); + struct aa_sk_ctx *ctx = aa_sock(sk); if (!skb->secmark) return 0; @@ -1328,6 +1464,7 @@ static struct security_hook_list apparmor_hooks[] __ro_after_init = { LSM_HOOK_INIT(task_getsecid_obj, apparmor_task_getsecid_obj), LSM_HOOK_INIT(task_setrlimit, apparmor_task_setrlimit), LSM_HOOK_INIT(task_kill, apparmor_task_kill), + LSM_HOOK_INIT(userns_create, apparmor_userns_create), #ifdef CONFIG_AUDIT LSM_HOOK_INIT(audit_rule_init, aa_audit_rule_init), @@ -1339,6 +1476,11 @@ static struct security_hook_list apparmor_hooks[] __ro_after_init = { LSM_HOOK_INIT(secid_to_secctx, apparmor_secid_to_secctx), LSM_HOOK_INIT(secctx_to_secid, apparmor_secctx_to_secid), LSM_HOOK_INIT(release_secctx, apparmor_release_secctx), + +#ifdef CONFIG_IO_URING + LSM_HOOK_INIT(uring_override_creds, apparmor_uring_override_creds), + LSM_HOOK_INIT(uring_sqpoll, apparmor_uring_sqpoll), +#endif }; /* @@ -1669,11 +1811,32 @@ static int param_set_mode(const char *val, const struct kernel_param *kp) char *aa_get_buffer(bool in_atomic) { union aa_buffer *aa_buf; + struct aa_local_cache *cache; bool try_again = true; gfp_t flags = (GFP_KERNEL | __GFP_RETRY_MAYFAIL | __GFP_NOWARN); + /* use per cpu cached buffers first */ + cache = get_cpu_ptr(&aa_local_buffers); + if (!list_empty(&cache->head)) { + aa_buf = list_first_entry(&cache->head, union aa_buffer, list); + list_del(&aa_buf->list); + cache->hold--; + cache->count--; + put_cpu_ptr(&aa_local_buffers); + return &aa_buf->buffer[0]; + } + put_cpu_ptr(&aa_local_buffers); + + if (!spin_trylock(&aa_buffers_lock)) { + cache = get_cpu_ptr(&aa_local_buffers); + cache->hold += 1; + put_cpu_ptr(&aa_local_buffers); + spin_lock(&aa_buffers_lock); + } else { + cache = get_cpu_ptr(&aa_local_buffers); + put_cpu_ptr(&aa_local_buffers); + } retry: - spin_lock(&aa_buffers_lock); if (buffer_count > reserve_count || (in_atomic && !list_empty(&aa_global_buffers))) { aa_buf = list_first_entry(&aa_global_buffers, union aa_buffer, @@ -1699,6 +1862,7 @@ retry: if (!aa_buf) { if (try_again) { try_again = false; + spin_lock(&aa_buffers_lock); goto retry; } pr_warn_once("AppArmor: Failed to allocate a memory buffer.\n"); @@ -1710,15 +1874,34 @@ retry: void aa_put_buffer(char *buf) { union aa_buffer *aa_buf; + struct aa_local_cache *cache; if (!buf) return; aa_buf = container_of(buf, union aa_buffer, buffer[0]); - spin_lock(&aa_buffers_lock); - list_add(&aa_buf->list, &aa_global_buffers); - buffer_count++; - spin_unlock(&aa_buffers_lock); + cache = get_cpu_ptr(&aa_local_buffers); + if (!cache->hold) { + put_cpu_ptr(&aa_local_buffers); + + if (spin_trylock(&aa_buffers_lock)) { + /* put back on global list */ + list_add(&aa_buf->list, &aa_global_buffers); + buffer_count++; + spin_unlock(&aa_buffers_lock); + cache = get_cpu_ptr(&aa_local_buffers); + put_cpu_ptr(&aa_local_buffers); + return; + } + /* contention on global list, fallback to percpu */ + cache = get_cpu_ptr(&aa_local_buffers); + cache->hold += 1; + } + + /* cache in percpu list */ + list_add(&aa_buf->list, &cache->head); + cache->count++; + put_cpu_ptr(&aa_local_buffers); } /* @@ -1761,6 +1944,15 @@ static int __init alloc_buffers(void) int i, num; /* + * per cpu set of cached allocated buffers used to help reduce + * lock contention + */ + for_each_possible_cpu(i) { + per_cpu(aa_local_buffers, i).hold = 0; + per_cpu(aa_local_buffers, i).count = 0; + INIT_LIST_HEAD(&per_cpu(aa_local_buffers, i).head); + } + /* * A function may require two buffers at once. Usually the buffers are * used for a short period of time and are shared. On UP kernel buffers * two should be enough, with more CPUs it is possible that more @@ -1799,6 +1991,7 @@ static int apparmor_dointvec(struct ctl_table *table, int write, } static struct ctl_table apparmor_sysctl_table[] = { +#ifdef CONFIG_USER_NS { .procname = "unprivileged_userns_apparmor_policy", .data = &unprivileged_userns_apparmor_policy, @@ -1806,6 +1999,7 @@ static struct ctl_table apparmor_sysctl_table[] = { .mode = 0600, .proc_handler = apparmor_dointvec, }, +#endif /* CONFIG_USER_NS */ { .procname = "apparmor_display_secid_mode", .data = &apparmor_display_secid_mode, @@ -1813,7 +2007,13 @@ static struct ctl_table apparmor_sysctl_table[] = { .mode = 0600, .proc_handler = apparmor_dointvec, }, - + { + .procname = "apparmor_restrict_unprivileged_unconfined", + .data = &aa_unprivileged_unconfined_restricted, + .maxlen = sizeof(int), + .mode = 0600, + .proc_handler = apparmor_dointvec, + }, { } }; @@ -1843,7 +2043,7 @@ static unsigned int apparmor_ip_postroute(void *priv, if (sk == NULL) return NF_ACCEPT; - ctx = SK_CTX(sk); + ctx = aa_sock(sk); if (!apparmor_secmark_check(ctx->label, OP_SENDMSG, AA_MAY_SEND, skb->secmark, sk)) return NF_ACCEPT; @@ -1902,6 +2102,69 @@ static int __init apparmor_nf_ip_init(void) __initcall(apparmor_nf_ip_init); #endif +static char nulldfa_src[] = { + #include "nulldfa.in" +}; +struct aa_dfa *nulldfa; + +static char stacksplitdfa_src[] = { + #include "stacksplitdfa.in" +}; +struct aa_dfa *stacksplitdfa; +struct aa_policydb *nullpdb; + +static int __init aa_setup_dfa_engine(void) +{ + int error = -ENOMEM; + + nullpdb = aa_alloc_pdb(GFP_KERNEL); + if (!nullpdb) + return -ENOMEM; + + nulldfa = aa_dfa_unpack(nulldfa_src, sizeof(nulldfa_src), + TO_ACCEPT1_FLAG(YYTD_DATA32) | + TO_ACCEPT2_FLAG(YYTD_DATA32)); + if (IS_ERR(nulldfa)) { + error = PTR_ERR(nulldfa); + goto fail; + } + nullpdb->dfa = aa_get_dfa(nulldfa); + nullpdb->perms = kcalloc(2, sizeof(struct aa_perms), GFP_KERNEL); + if (!nullpdb->perms) + goto fail; + nullpdb->size = 2; + + stacksplitdfa = aa_dfa_unpack(stacksplitdfa_src, + sizeof(stacksplitdfa_src), + TO_ACCEPT1_FLAG(YYTD_DATA32) | + TO_ACCEPT2_FLAG(YYTD_DATA32)); + if (IS_ERR(stacksplitdfa)) { + error = PTR_ERR(stacksplitdfa); + goto fail; + } + + return 0; + +fail: + aa_put_pdb(nullpdb); + aa_put_dfa(nulldfa); + nullpdb = NULL; + nulldfa = NULL; + stacksplitdfa = NULL; + + return error; +} + +static void __init aa_teardown_dfa_engine(void) +{ + aa_put_dfa(stacksplitdfa); + aa_put_dfa(nulldfa); + aa_put_pdb(nullpdb); + nullpdb = NULL; + stacksplitdfa = NULL; + nulldfa = NULL; +} + static int __init apparmor_init(void) { int error; diff --git a/security/apparmor/match.c b/security/apparmor/match.c index b97ef5e1db..517d77d3c3 100644 --- a/security/apparmor/match.c +++ b/security/apparmor/match.c @@ -21,50 +21,6 @@ #define base_idx(X) ((X) & 0xffffff) -static char nulldfa_src[] = { - #include "nulldfa.in" -}; -struct aa_dfa *nulldfa; - -static char stacksplitdfa_src[] = { - #include "stacksplitdfa.in" -}; -struct aa_dfa *stacksplitdfa; - -int __init aa_setup_dfa_engine(void) -{ - int error; - - nulldfa = aa_dfa_unpack(nulldfa_src, sizeof(nulldfa_src), - TO_ACCEPT1_FLAG(YYTD_DATA32) | - TO_ACCEPT2_FLAG(YYTD_DATA32)); - if (IS_ERR(nulldfa)) { - error = PTR_ERR(nulldfa); - nulldfa = NULL; - return error; - } - - stacksplitdfa = aa_dfa_unpack(stacksplitdfa_src, - sizeof(stacksplitdfa_src), - TO_ACCEPT1_FLAG(YYTD_DATA32) | - TO_ACCEPT2_FLAG(YYTD_DATA32)); - if (IS_ERR(stacksplitdfa)) { - aa_put_dfa(nulldfa); - nulldfa = NULL; - error = PTR_ERR(stacksplitdfa); - stacksplitdfa = NULL; - return error; - } - - return 0; -} - -void __init aa_teardown_dfa_engine(void) -{ - aa_put_dfa(stacksplitdfa); - aa_put_dfa(nulldfa); -} - /** * unpack_table - unpack a dfa table (one of accept, default, base, next check) * @blob: data to unpack (NOT NULL) @@ -136,7 +92,7 @@ fail: /** * verify_table_headers - verify that the tables headers are as expected - * @tables - array of dfa tables to check (NOT NULL) + * @tables: array of dfa tables to check (NOT NULL) * @flags: flags controlling what type of accept table are acceptable * * Assumes dfa has gone through the first pass verification done by unpacking @@ -283,7 +239,7 @@ static void dfa_free(struct aa_dfa *dfa) /** * aa_dfa_free_kref - free aa_dfa by kref (called by aa_put_dfa) - * @kr: kref callback for freeing of a dfa (NOT NULL) + * @kref: kref callback for freeing of a dfa (NOT NULL) */ void aa_dfa_free_kref(struct kref *kref) { diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c index cb0fdbdb82..49fe8da6fe 100644 --- a/security/apparmor/mount.c +++ b/security/apparmor/mount.c @@ -332,8 +332,8 @@ static int match_mnt_path_str(const struct cred *subj_cred, } error = -EACCES; - pos = do_match_mnt(&rules->policy, - rules->policy.start[AA_CLASS_MOUNT], + pos = do_match_mnt(rules->policy, + rules->policy->start[AA_CLASS_MOUNT], mntpnt, devname, type, flags, data, binary, &perms); if (pos) { info = mnt_info_table[pos]; @@ -620,10 +620,10 @@ static int profile_umount(const struct cred *subj_cred, if (error) goto audit; - state = aa_dfa_match(rules->policy.dfa, - rules->policy.start[AA_CLASS_MOUNT], + state = aa_dfa_match(rules->policy->dfa, + rules->policy->start[AA_CLASS_MOUNT], name); - perms = *aa_lookup_perms(&rules->policy, state); + perms = *aa_lookup_perms(rules->policy, state); if (AA_MAY_UMOUNT & ~perms.allow) error = -EACCES; @@ -694,12 +694,12 @@ static struct aa_label *build_pivotroot(const struct cred *subj_cred, goto audit; error = -EACCES; - state = aa_dfa_match(rules->policy.dfa, - rules->policy.start[AA_CLASS_MOUNT], + state = aa_dfa_match(rules->policy->dfa, + rules->policy->start[AA_CLASS_MOUNT], new_name); - state = aa_dfa_null_transition(rules->policy.dfa, state); - state = aa_dfa_match(rules->policy.dfa, state, old_name); - perms = *aa_lookup_perms(&rules->policy, state); + state = aa_dfa_null_transition(rules->policy->dfa, state); + state = aa_dfa_match(rules->policy->dfa, state, old_name); + perms = *aa_lookup_perms(rules->policy, state); if (AA_MAY_PIVOTROOT & perms.allow) error = 0; diff --git a/security/apparmor/net.c b/security/apparmor/net.c index 704c171232..87e934b2b5 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -127,9 +127,9 @@ int aa_profile_af_perm(struct aa_profile *profile, buffer[0] = cpu_to_be16(family); buffer[1] = cpu_to_be16((u16) type); - state = aa_dfa_match_len(rules->policy.dfa, state, (char *) &buffer, + state = aa_dfa_match_len(rules->policy->dfa, state, (char *) &buffer, 4); - perms = *aa_lookup_perms(&rules->policy, state); + perms = *aa_lookup_perms(rules->policy, state); aa_apply_modes_to_perms(profile, &perms); return aa_check_perms(profile, &perms, request, ad, audit_net_cb); diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 8a07793ce1..957654d253 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -88,6 +88,7 @@ #include "include/resource.h" int unprivileged_userns_apparmor_policy = 1; +int aa_unprivileged_unconfined_restricted; const char *const aa_profile_mode_names[] = { "enforce", @@ -98,6 +99,42 @@ const char *const aa_profile_mode_names[] = { }; +static void aa_free_pdb(struct aa_policydb *pdb) +{ + if (pdb) { + aa_put_dfa(pdb->dfa); + if (pdb->perms) + kvfree(pdb->perms); + aa_free_str_table(&pdb->trans); + kfree(pdb); + } +} + +/** + * aa_pdb_free_kref - free aa_policydb by kref (called by aa_put_pdb) + * @kref: kref callback for freeing of a dfa (NOT NULL) + */ +void aa_pdb_free_kref(struct kref *kref) +{ + struct aa_policydb *pdb = container_of(kref, struct aa_policydb, count); + + aa_free_pdb(pdb); +} + + +struct aa_policydb *aa_alloc_pdb(gfp_t gfp) +{ + struct aa_policydb *pdb = kzalloc(sizeof(struct aa_policydb), gfp); + + if (!pdb) + return NULL; + + kref_init(&pdb->count); + + return pdb; +} + + /** * __add_profile - add a profiles to list and label tree * @list: list to add it to (NOT NULL) @@ -200,15 +237,15 @@ static void free_attachment(struct aa_attachment *attach) for (i = 0; i < attach->xattr_count; i++) kfree_sensitive(attach->xattrs[i]); kfree_sensitive(attach->xattrs); - aa_destroy_policydb(&attach->xmatch); + aa_put_pdb(attach->xmatch); } static void free_ruleset(struct aa_ruleset *rules) { int i; - aa_destroy_policydb(&rules->file); - aa_destroy_policydb(&rules->policy); + aa_put_pdb(rules->file); + aa_put_pdb(rules->policy); aa_free_cap_rules(&rules->caps); aa_free_rlimit_rules(&rules->rlimits); @@ -590,16 +627,8 @@ struct aa_profile *aa_alloc_null(struct aa_profile *parent, const char *name, /* TODO: ideally we should inherit abi from parent */ profile->label.flags |= FLAG_NULL; rules = list_first_entry(&profile->rules, typeof(*rules), list); - rules->file.dfa = aa_get_dfa(nulldfa); - rules->file.perms = kcalloc(2, sizeof(struct aa_perms), GFP_KERNEL); - if (!rules->file.perms) - goto fail; - rules->file.size = 2; - rules->policy.dfa = aa_get_dfa(nulldfa); - rules->policy.perms = kcalloc(2, sizeof(struct aa_perms), GFP_KERNEL); - if (!rules->policy.perms) - goto fail; - rules->policy.size = 2; + rules->file = aa_get_pdb(nullpdb); + rules->policy = aa_get_pdb(nullpdb); if (parent) { profile->path_flags = parent->path_flags; @@ -610,11 +639,6 @@ struct aa_profile *aa_alloc_null(struct aa_profile *parent, const char *name, } return profile; - -fail: - aa_free_profile(profile); - - return NULL; } /** @@ -847,7 +871,7 @@ bool aa_current_policy_admin_capable(struct aa_ns *ns) /** * aa_may_manage_policy - can the current task manage policy - * @subj_cred; subjects cred + * @subj_cred: subjects cred * @label: label to check if it can manage policy * @ns: namespace being managed by @label (may be NULL if @label's ns) * @mask: contains the policy manipulation operation being done diff --git a/security/apparmor/policy_compat.c b/security/apparmor/policy_compat.c index 0cb02da8a3..423227670e 100644 --- a/security/apparmor/policy_compat.c +++ b/security/apparmor/policy_compat.c @@ -143,6 +143,7 @@ static struct aa_perms compute_fperms_other(struct aa_dfa *dfa, * compute_fperms - convert dfa compressed perms to internal perms and store * them so they can be retrieved later. * @dfa: a dfa using fperms to remap to internal permissions + * @size: Returns the permission table size * * Returns: remapped perm table */ diff --git a/security/apparmor/policy_ns.c b/security/apparmor/policy_ns.c index fd5b7afbcb..1f02cfe1d9 100644 --- a/security/apparmor/policy_ns.c +++ b/security/apparmor/policy_ns.c @@ -160,43 +160,6 @@ void aa_free_ns(struct aa_ns *ns) } /** - * aa_findn_ns - look up a profile namespace on the namespace list - * @root: namespace to search in (NOT NULL) - * @name: name of namespace to find (NOT NULL) - * @n: length of @name - * - * Returns: a refcounted namespace on the list, or NULL if no namespace - * called @name exists. - * - * refcount released by caller - */ -struct aa_ns *aa_findn_ns(struct aa_ns *root, const char *name, size_t n) -{ - struct aa_ns *ns = NULL; - - rcu_read_lock(); - ns = aa_get_ns(__aa_findn_ns(&root->sub_ns, name, n)); - rcu_read_unlock(); - - return ns; -} - -/** - * aa_find_ns - look up a profile namespace on the namespace list - * @root: namespace to search in (NOT NULL) - * @name: name of namespace to find (NOT NULL) - * - * Returns: a refcounted namespace on the list, or NULL if no namespace - * called @name exists. - * - * refcount released by caller - */ -struct aa_ns *aa_find_ns(struct aa_ns *root, const char *name) -{ - return aa_findn_ns(root, name, strlen(name)); -} - -/** * __aa_lookupn_ns - lookup the namespace matching @hname * @view: namespace to search in (NOT NULL) * @hname: hierarchical ns name (NOT NULL) diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index d92788da67..5e578ef0dd 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -90,10 +90,10 @@ void __aa_loaddata_update(struct aa_loaddata *data, long revision) struct inode *inode; inode = d_inode(data->dents[AAFS_LOADDATA_DIR]); - inode->i_mtime = inode_set_ctime_current(inode); + inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); inode = d_inode(data->dents[AAFS_LOADDATA_REVISION]); - inode->i_mtime = inode_set_ctime_current(inode); + inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); } } @@ -705,24 +705,29 @@ fail_reset: return -EPROTO; } -static int unpack_pdb(struct aa_ext *e, struct aa_policydb *policy, +static int unpack_pdb(struct aa_ext *e, struct aa_policydb **policy, bool required_dfa, bool required_trans, const char **info) { + struct aa_policydb *pdb; void *pos = e->pos; int i, flags, error = -EPROTO; ssize_t size; - size = unpack_perms_table(e, &policy->perms); + pdb = aa_alloc_pdb(GFP_KERNEL); + if (!pdb) + return -ENOMEM; + + size = unpack_perms_table(e, &pdb->perms); if (size < 0) { error = size; - policy->perms = NULL; + pdb->perms = NULL; *info = "failed to unpack - perms"; goto fail; } - policy->size = size; + pdb->size = size; - if (policy->perms) { + if (pdb->perms) { /* perms table present accept is index */ flags = TO_ACCEPT1_FLAG(YYTD_DATA32); } else { @@ -731,13 +736,13 @@ static int unpack_pdb(struct aa_ext *e, struct aa_policydb *policy, TO_ACCEPT2_FLAG(YYTD_DATA32); } - policy->dfa = unpack_dfa(e, flags); - if (IS_ERR(policy->dfa)) { - error = PTR_ERR(policy->dfa); - policy->dfa = NULL; + pdb->dfa = unpack_dfa(e, flags); + if (IS_ERR(pdb->dfa)) { + error = PTR_ERR(pdb->dfa); + pdb->dfa = NULL; *info = "failed to unpack - dfa"; goto fail; - } else if (!policy->dfa) { + } else if (!pdb->dfa) { if (required_dfa) { *info = "missing required dfa"; goto fail; @@ -751,18 +756,18 @@ static int unpack_pdb(struct aa_ext *e, struct aa_policydb *policy, * sadly start was given different names for file and policydb * but since it is optional we can try both */ - if (!aa_unpack_u32(e, &policy->start[0], "start")) + if (!aa_unpack_u32(e, &pdb->start[0], "start")) /* default start state */ - policy->start[0] = DFA_START; - if (!aa_unpack_u32(e, &policy->start[AA_CLASS_FILE], "dfa_start")) { + pdb->start[0] = DFA_START; + if (!aa_unpack_u32(e, &pdb->start[AA_CLASS_FILE], "dfa_start")) { /* default start state for xmatch and file dfa */ - policy->start[AA_CLASS_FILE] = DFA_START; + pdb->start[AA_CLASS_FILE] = DFA_START; } /* setup class index */ for (i = AA_CLASS_FILE + 1; i <= AA_CLASS_LAST; i++) { - policy->start[i] = aa_dfa_next(policy->dfa, policy->start[0], + pdb->start[i] = aa_dfa_next(pdb->dfa, pdb->start[0], i); } - if (!unpack_trans_table(e, &policy->trans) && required_trans) { + if (!unpack_trans_table(e, &pdb->trans) && required_trans) { *info = "failed to unpack profile transition table"; goto fail; } @@ -770,9 +775,11 @@ static int unpack_pdb(struct aa_ext *e, struct aa_policydb *policy, /* TODO: move compat mapping here, requires dfa merging first */ /* TODO: move verify here, it has to be done after compat mappings */ out: + *policy = pdb; return 0; fail: + aa_put_pdb(pdb); e->pos = pos; return error; } @@ -860,15 +867,15 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) } /* neither xmatch_len not xmatch_perms are optional if xmatch is set */ - if (profile->attach.xmatch.dfa) { + if (profile->attach.xmatch->dfa) { if (!aa_unpack_u32(e, &tmp, NULL)) { info = "missing xmatch len"; goto fail; } profile->attach.xmatch_len = tmp; - profile->attach.xmatch.start[AA_CLASS_XMATCH] = DFA_START; - if (!profile->attach.xmatch.perms) { - error = aa_compat_map_xmatch(&profile->attach.xmatch); + profile->attach.xmatch->start[AA_CLASS_XMATCH] = DFA_START; + if (!profile->attach.xmatch->perms) { + error = aa_compat_map_xmatch(profile->attach.xmatch); if (error) { info = "failed to convert xmatch permission table"; goto fail; @@ -985,16 +992,16 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) if (error) goto fail; /* Fixup: drop when we get rid of start array */ - if (aa_dfa_next(rules->policy.dfa, rules->policy.start[0], + if (aa_dfa_next(rules->policy->dfa, rules->policy->start[0], AA_CLASS_FILE)) - rules->policy.start[AA_CLASS_FILE] = - aa_dfa_next(rules->policy.dfa, - rules->policy.start[0], + rules->policy->start[AA_CLASS_FILE] = + aa_dfa_next(rules->policy->dfa, + rules->policy->start[0], AA_CLASS_FILE); if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) goto fail; - if (!rules->policy.perms) { - error = aa_compat_map_policy(&rules->policy, + if (!rules->policy->perms) { + error = aa_compat_map_policy(rules->policy, e->version); if (error) { info = "failed to remap policydb permission table"; @@ -1002,44 +1009,27 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) } } } else { - rules->policy.dfa = aa_get_dfa(nulldfa); - rules->policy.perms = kcalloc(2, sizeof(struct aa_perms), - GFP_KERNEL); - if (!rules->policy.perms) - goto fail; - rules->policy.size = 2; + rules->policy = aa_get_pdb(nullpdb); } /* get file rules */ error = unpack_pdb(e, &rules->file, false, true, &info); if (error) { goto fail; - } else if (rules->file.dfa) { - if (!rules->file.perms) { - error = aa_compat_map_file(&rules->file); + } else if (rules->file->dfa) { + if (!rules->file->perms) { + error = aa_compat_map_file(rules->file); if (error) { info = "failed to remap file permission table"; goto fail; } } - } else if (rules->policy.dfa && - rules->policy.start[AA_CLASS_FILE]) { - rules->file.dfa = aa_get_dfa(rules->policy.dfa); - rules->file.start[AA_CLASS_FILE] = rules->policy.start[AA_CLASS_FILE]; - rules->file.perms = kcalloc(rules->policy.size, - sizeof(struct aa_perms), - GFP_KERNEL); - if (!rules->file.perms) - goto fail; - memcpy(rules->file.perms, rules->policy.perms, - rules->policy.size * sizeof(struct aa_perms)); - rules->file.size = rules->policy.size; + } else if (rules->policy->dfa && + rules->policy->start[AA_CLASS_FILE]) { + aa_put_pdb(rules->file); + rules->file = aa_get_pdb(rules->policy); } else { - rules->file.dfa = aa_get_dfa(nulldfa); - rules->file.perms = kcalloc(2, sizeof(struct aa_perms), - GFP_KERNEL); - if (!rules->file.perms) - goto fail; - rules->file.size = 2; + aa_put_pdb(rules->file); + rules->file = aa_get_pdb(nullpdb); } error = -EPROTO; if (aa_unpack_nameX(e, AA_STRUCT, "data")) { @@ -1175,7 +1165,7 @@ static int verify_header(struct aa_ext *e, int required, const char **ns) /** * verify_dfa_accept_index - verify accept indexes are in range of perms table * @dfa: the dfa to check accept indexes are in range - * table_size: the permission table size the indexes should be within + * @table_size: the permission table size the indexes should be within */ static bool verify_dfa_accept_index(struct aa_dfa *dfa, int table_size) { @@ -1246,26 +1236,32 @@ static int verify_profile(struct aa_profile *profile) if (!rules) return 0; - if ((rules->file.dfa && !verify_dfa_accept_index(rules->file.dfa, - rules->file.size)) || - (rules->policy.dfa && - !verify_dfa_accept_index(rules->policy.dfa, rules->policy.size))) { + if (rules->file->dfa && !verify_dfa_accept_index(rules->file->dfa, + rules->file->size)) { + audit_iface(profile, NULL, NULL, + "Unpack: file Invalid named transition", NULL, + -EPROTO); + return -EPROTO; + } + if (rules->policy->dfa && + !verify_dfa_accept_index(rules->policy->dfa, rules->policy->size)) { audit_iface(profile, NULL, NULL, - "Unpack: Invalid named transition", NULL, -EPROTO); + "Unpack: policy Invalid named transition", NULL, + -EPROTO); return -EPROTO; } - if (!verify_perms(&rules->file)) { + if (!verify_perms(rules->file)) { audit_iface(profile, NULL, NULL, "Unpack: Invalid perm index", NULL, -EPROTO); return -EPROTO; } - if (!verify_perms(&rules->policy)) { + if (!verify_perms(rules->policy)) { audit_iface(profile, NULL, NULL, "Unpack: Invalid perm index", NULL, -EPROTO); return -EPROTO; } - if (!verify_perms(&profile->attach.xmatch)) { + if (!verify_perms(profile->attach.xmatch)) { audit_iface(profile, NULL, NULL, "Unpack: Invalid perm index", NULL, -EPROTO); return -EPROTO; diff --git a/security/apparmor/task.c b/security/apparmor/task.c index 0d7af707cc..f29a2e80e6 100644 --- a/security/apparmor/task.c +++ b/security/apparmor/task.c @@ -93,9 +93,8 @@ int aa_replace_current_label(struct aa_label *label) * aa_set_current_onexec - set the tasks change_profile to happen onexec * @label: system label to set at exec (MAYBE NULL to clear value) * @stack: whether stacking should be done - * Returns: 0 or error on failure */ -int aa_set_current_onexec(struct aa_label *label, bool stack) +void aa_set_current_onexec(struct aa_label *label, bool stack) { struct aa_task_ctx *ctx = task_ctx(current); @@ -103,8 +102,6 @@ int aa_set_current_onexec(struct aa_label *label, bool stack) aa_put_label(ctx->onexec); ctx->onexec = label; ctx->token = stack; - - return 0; } /** @@ -301,3 +298,44 @@ int aa_may_ptrace(const struct cred *tracer_cred, struct aa_label *tracer, profile_tracee_perm(tracee_cred, profile, tracer, xrequest, &sa)); } + +/* call back to audit ptrace fields */ +static void audit_ns_cb(struct audit_buffer *ab, void *va) +{ + struct apparmor_audit_data *ad = aad_of_va(va); + + if (ad->request & AA_USERNS_CREATE) + audit_log_format(ab, " requested=\"userns_create\""); + + if (ad->denied & AA_USERNS_CREATE) + audit_log_format(ab, " denied=\"userns_create\""); +} + +int aa_profile_ns_perm(struct aa_profile *profile, + struct apparmor_audit_data *ad, + u32 request) +{ + struct aa_perms perms = { }; + int error = 0; + + ad->subj_label = &profile->label; + ad->request = request; + + if (!profile_unconfined(profile)) { + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), + list); + aa_state_t state; + + state = RULE_MEDIATES(rules, ad->class); + if (!state) + /* TODO: add flag to complain about unmediated */ + return 0; + perms = *aa_lookup_perms(rules->policy, state); + aa_apply_modes_to_perms(profile, &perms); + error = aa_check_perms(profile, &perms, request, ad, + audit_ns_cb); + } + + return error; +} diff --git a/security/commoncap.c b/security/commoncap.c index bc05211041..8e8c630ce2 100644 --- a/security/commoncap.c +++ b/security/commoncap.c @@ -720,7 +720,7 @@ int get_vfs_caps_from_disk(struct mnt_idmap *idmap, * its xattrs and, if present, apply them to the proposed credentials being * constructed by execve(). */ -static int get_file_caps(struct linux_binprm *bprm, struct file *file, +static int get_file_caps(struct linux_binprm *bprm, const struct file *file, bool *effective, bool *has_fcap) { int rc = 0; @@ -882,7 +882,7 @@ static inline bool nonroot_raised_pE(struct cred *new, const struct cred *old, * * Return: 0 if successful, -ve on error. */ -int cap_bprm_creds_from_file(struct linux_binprm *bprm, struct file *file) +int cap_bprm_creds_from_file(struct linux_binprm *bprm, const struct file *file) { /* Process setpcap binaries and capabilities for uid 0 */ const struct cred *old = current_cred(); diff --git a/security/inode.c b/security/inode.c index 3aa75fffa8..9e7cde9136 100644 --- a/security/inode.c +++ b/security/inode.c @@ -145,7 +145,7 @@ static struct dentry *securityfs_create_dentry(const char *name, umode_t mode, inode->i_ino = get_next_ino(); inode->i_mode = mode; - inode->i_atime = inode->i_mtime = inode_set_ctime_current(inode); + simple_inode_init_ts(inode); inode->i_private = data; if (S_ISDIR(mode)) { inode->i_op = &simple_dir_inode_operations; diff --git a/security/integrity/Kconfig b/security/integrity/Kconfig index b6e074ac02..3c45f4f345 100644 --- a/security/integrity/Kconfig +++ b/security/integrity/Kconfig @@ -34,10 +34,10 @@ config INTEGRITY_ASYMMETRIC_KEYS bool "Enable asymmetric keys support" depends on INTEGRITY_SIGNATURE default n - select ASYMMETRIC_KEY_TYPE - select ASYMMETRIC_PUBLIC_KEY_SUBTYPE - select CRYPTO_RSA - select X509_CERTIFICATE_PARSER + select ASYMMETRIC_KEY_TYPE + select ASYMMETRIC_PUBLIC_KEY_SUBTYPE + select CRYPTO_RSA + select X509_CERTIFICATE_PARSER help This option enables digital signature verification using asymmetric keys. @@ -53,14 +53,14 @@ config INTEGRITY_TRUSTED_KEYRING keyring. config INTEGRITY_PLATFORM_KEYRING - bool "Provide keyring for platform/firmware trusted keys" - depends on INTEGRITY_ASYMMETRIC_KEYS - depends on SYSTEM_BLACKLIST_KEYRING - help - Provide a separate, distinct keyring for platform trusted keys, which - the kernel automatically populates during initialization from values - provided by the platform for verifying the kexec'ed kerned image - and, possibly, the initramfs signature. + bool "Provide keyring for platform/firmware trusted keys" + depends on INTEGRITY_ASYMMETRIC_KEYS + depends on SYSTEM_BLACKLIST_KEYRING + help + Provide a separate, distinct keyring for platform trusted keys, which + the kernel automatically populates during initialization from values + provided by the platform for verifying the kexec'ed kerned image + and, possibly, the initramfs signature. config INTEGRITY_MACHINE_KEYRING bool "Provide a keyring to which Machine Owner Keys may be added" @@ -69,10 +69,10 @@ config INTEGRITY_MACHINE_KEYRING depends on SYSTEM_BLACKLIST_KEYRING depends on LOAD_UEFI_KEYS || LOAD_PPC_KEYS help - If set, provide a keyring to which Machine Owner Keys (MOK) may - be added. This keyring shall contain just MOK keys. Unlike keys - in the platform keyring, keys contained in the .machine keyring will - be trusted within the kernel. + If set, provide a keyring to which Machine Owner Keys (MOK) may + be added. This keyring shall contain just MOK keys. Unlike keys + in the platform keyring, keys contained in the .machine keyring will + be trusted within the kernel. config INTEGRITY_CA_MACHINE_KEYRING bool "Enforce Machine Keyring CA Restrictions" @@ -97,14 +97,14 @@ config INTEGRITY_CA_MACHINE_KEYRING_MAX .platform keyring. config LOAD_UEFI_KEYS - depends on INTEGRITY_PLATFORM_KEYRING - depends on EFI - def_bool y + depends on INTEGRITY_PLATFORM_KEYRING + depends on EFI + def_bool y config LOAD_IPL_KEYS - depends on INTEGRITY_PLATFORM_KEYRING - depends on S390 - def_bool y + depends on INTEGRITY_PLATFORM_KEYRING + depends on S390 + def_bool y config LOAD_PPC_KEYS bool "Enable loading of platform and blacklisted keys for POWER" diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index ff9a939dad..894570fe39 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -14,7 +14,6 @@ #define pr_fmt(fmt) "EVM: "fmt #include <linux/init.h> -#include <linux/crypto.h> #include <linux/audit.h> #include <linux/xattr.h> #include <linux/integrity.h> @@ -25,7 +24,7 @@ #include <crypto/hash.h> #include <crypto/hash_info.h> -#include <crypto/algapi.h> +#include <crypto/utils.h> #include "evm.h" int evm_initialized; diff --git a/security/integrity/iint.c b/security/integrity/iint.c index 27ea19fb1f..d4419a2a1e 100644 --- a/security/integrity/iint.c +++ b/security/integrity/iint.c @@ -23,7 +23,7 @@ static struct rb_root integrity_iint_tree = RB_ROOT; static DEFINE_RWLOCK(integrity_iint_lock); -static struct kmem_cache *iint_cache __read_mostly; +static struct kmem_cache *iint_cache __ro_after_init; struct dentry *integrity_dir; diff --git a/security/integrity/ima/ima_modsig.c b/security/integrity/ima/ima_modsig.c index 3e7bee3008..3265d744d5 100644 --- a/security/integrity/ima/ima_modsig.c +++ b/security/integrity/ima/ima_modsig.c @@ -29,7 +29,7 @@ struct modsig { * storing the signature. */ int raw_pkcs7_len; - u8 raw_pkcs7[]; + u8 raw_pkcs7[] __counted_by(raw_pkcs7_len); }; /* @@ -65,10 +65,11 @@ int ima_read_modsig(enum ima_hooks func, const void *buf, loff_t buf_len, buf_len -= sig_len + sizeof(*sig); /* Allocate sig_len additional bytes to hold the raw PKCS#7 data. */ - hdr = kzalloc(sizeof(*hdr) + sig_len, GFP_KERNEL); + hdr = kzalloc(struct_size(hdr, raw_pkcs7, sig_len), GFP_KERNEL); if (!hdr) return -ENOMEM; + hdr->raw_pkcs7_len = sig_len; hdr->pkcs7_msg = pkcs7_parse_message(buf + buf_len, sig_len); if (IS_ERR(hdr->pkcs7_msg)) { rc = PTR_ERR(hdr->pkcs7_msg); @@ -77,7 +78,6 @@ int ima_read_modsig(enum ima_hooks func, const void *buf, loff_t buf_len, } memcpy(hdr->raw_pkcs7, buf + buf_len, sig_len); - hdr->raw_pkcs7_len = sig_len; /* We don't know the hash algorithm yet. */ hdr->hash_algo = HASH_ALGO__LAST; diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c index 1e313982af..8af2136069 100644 --- a/security/keys/encrypted-keys/encrypted.c +++ b/security/keys/encrypted-keys/encrypted.c @@ -27,10 +27,10 @@ #include <linux/scatterlist.h> #include <linux/ctype.h> #include <crypto/aes.h> -#include <crypto/algapi.h> #include <crypto/hash.h> #include <crypto/sha2.h> #include <crypto/skcipher.h> +#include <crypto/utils.h> #include "encrypted.h" #include "ecryptfs_format.h" diff --git a/security/keys/internal.h b/security/keys/internal.h index ec2ec335b6..2cffa6dc82 100644 --- a/security/keys/internal.h +++ b/security/keys/internal.h @@ -109,13 +109,6 @@ extern void __key_link_end(struct key *keyring, extern key_ref_t find_key_to_update(key_ref_t keyring_ref, const struct keyring_index_key *index_key); -extern struct key *keyring_search_instkey(struct key *keyring, - key_serial_t target_id); - -extern int iterate_over_keyring(const struct key *keyring, - int (*func)(const struct key *key, void *data), - void *data); - struct keyring_search_context { struct keyring_index_key index_key; const struct cred *cred; diff --git a/security/keys/key.c b/security/keys/key.c index 5f103b2713..5b10641deb 100644 --- a/security/keys/key.c +++ b/security/keys/key.c @@ -690,6 +690,7 @@ error: spin_unlock(&key_serial_lock); return key; } +EXPORT_SYMBOL(key_lookup); /* * Find and lock the specified key type against removal. diff --git a/security/landlock/Kconfig b/security/landlock/Kconfig index c1e862a384..c4bf0d5eff 100644 --- a/security/landlock/Kconfig +++ b/security/landlock/Kconfig @@ -3,6 +3,7 @@ config SECURITY_LANDLOCK bool "Landlock support" depends on SECURITY + select SECURITY_NETWORK select SECURITY_PATH help Landlock is a sandboxing mechanism that enables processes to restrict diff --git a/security/landlock/Makefile b/security/landlock/Makefile index 7bbd2f413b..c2e116f2a2 100644 --- a/security/landlock/Makefile +++ b/security/landlock/Makefile @@ -2,3 +2,5 @@ obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o landlock-y := setup.o syscalls.o object.o ruleset.o \ cred.o ptrace.o fs.o + +landlock-$(CONFIG_INET) += net.o diff --git a/security/landlock/fs.c b/security/landlock/fs.c index 1c0c198f6f..bc7c126dee 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -151,16 +151,6 @@ retry: /* clang-format on */ /* - * All access rights that are denied by default whether they are handled or not - * by a ruleset/layer. This must be ORed with all ruleset->fs_access_masks[] - * entries when we need to get the absolute handled access masks. - */ -/* clang-format off */ -#define ACCESS_INITIALLY_DENIED ( \ - LANDLOCK_ACCESS_FS_REFER) -/* clang-format on */ - -/* * @path: Should have been checked by get_path_from_fd(). */ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset, @@ -168,7 +158,9 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset, access_mask_t access_rights) { int err; - struct landlock_object *object; + struct landlock_id id = { + .type = LANDLOCK_KEY_INODE, + }; /* Files only get access rights that make sense. */ if (!d_is_dir(path->dentry) && @@ -178,20 +170,19 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset, return -EINVAL; /* Transforms relative access rights to absolute ones. */ - access_rights |= - LANDLOCK_MASK_ACCESS_FS & - ~(ruleset->fs_access_masks[0] | ACCESS_INITIALLY_DENIED); - object = get_inode_object(d_backing_inode(path->dentry)); - if (IS_ERR(object)) - return PTR_ERR(object); + access_rights |= LANDLOCK_MASK_ACCESS_FS & + ~landlock_get_fs_access_mask(ruleset, 0); + id.key.object = get_inode_object(d_backing_inode(path->dentry)); + if (IS_ERR(id.key.object)) + return PTR_ERR(id.key.object); mutex_lock(&ruleset->lock); - err = landlock_insert_rule(ruleset, object, access_rights); + err = landlock_insert_rule(ruleset, id, access_rights); mutex_unlock(&ruleset->lock); /* * No need to check for an error because landlock_insert_rule() * increments the refcount for the new object if needed. */ - landlock_put_object(object); + landlock_put_object(id.key.object); return err; } @@ -208,6 +199,9 @@ find_rule(const struct landlock_ruleset *const domain, { const struct landlock_rule *rule; const struct inode *inode; + struct landlock_id id = { + .type = LANDLOCK_KEY_INODE, + }; /* Ignores nonexistent leafs. */ if (d_is_negative(dentry)) @@ -215,67 +209,13 @@ find_rule(const struct landlock_ruleset *const domain, inode = d_backing_inode(dentry); rcu_read_lock(); - rule = landlock_find_rule( - domain, rcu_dereference(landlock_inode(inode)->object)); + id.key.object = rcu_dereference(landlock_inode(inode)->object); + rule = landlock_find_rule(domain, id); rcu_read_unlock(); return rule; } /* - * @layer_masks is read and may be updated according to the access request and - * the matching rule. - * - * Returns true if the request is allowed (i.e. relevant layer masks for the - * request are empty). - */ -static inline bool -unmask_layers(const struct landlock_rule *const rule, - const access_mask_t access_request, - layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]) -{ - size_t layer_level; - - if (!access_request || !layer_masks) - return true; - if (!rule) - return false; - - /* - * An access is granted if, for each policy layer, at least one rule - * encountered on the pathwalk grants the requested access, - * regardless of its position in the layer stack. We must then check - * the remaining layers for each inode, from the first added layer to - * the last one. When there is multiple requested accesses, for each - * policy layer, the full set of requested accesses may not be granted - * by only one rule, but by the union (binary OR) of multiple rules. - * E.g. /a/b <execute> + /a <read> => /a/b <execute + read> - */ - for (layer_level = 0; layer_level < rule->num_layers; layer_level++) { - const struct landlock_layer *const layer = - &rule->layers[layer_level]; - const layer_mask_t layer_bit = BIT_ULL(layer->level - 1); - const unsigned long access_req = access_request; - unsigned long access_bit; - bool is_empty; - - /* - * Records in @layer_masks which layer grants access to each - * requested access. - */ - is_empty = true; - for_each_set_bit(access_bit, &access_req, - ARRAY_SIZE(*layer_masks)) { - if (layer->access & BIT_ULL(access_bit)) - (*layer_masks)[access_bit] &= ~layer_bit; - is_empty = is_empty && !(*layer_masks)[access_bit]; - } - if (is_empty) - return true; - } - return false; -} - -/* * Allows access to pseudo filesystems that will never be mountable (e.g. * sockfs, pipefs), but can still be reachable through * /proc/<pid>/fd/<file-descriptor> @@ -287,64 +227,35 @@ static inline bool is_nouser_or_private(const struct dentry *dentry) unlikely(IS_PRIVATE(d_backing_inode(dentry)))); } -static inline access_mask_t -get_handled_accesses(const struct landlock_ruleset *const domain) +static access_mask_t +get_raw_handled_fs_accesses(const struct landlock_ruleset *const domain) { - access_mask_t access_dom = ACCESS_INITIALLY_DENIED; + access_mask_t access_dom = 0; size_t layer_level; for (layer_level = 0; layer_level < domain->num_layers; layer_level++) - access_dom |= domain->fs_access_masks[layer_level]; - return access_dom & LANDLOCK_MASK_ACCESS_FS; + access_dom |= + landlock_get_raw_fs_access_mask(domain, layer_level); + return access_dom; } -/** - * init_layer_masks - Initialize layer masks from an access request - * - * Populates @layer_masks such that for each access right in @access_request, - * the bits for all the layers are set where this access right is handled. - * - * @domain: The domain that defines the current restrictions. - * @access_request: The requested access rights to check. - * @layer_masks: The layer masks to populate. - * - * Returns: An access mask where each access right bit is set which is handled - * in any of the active layers in @domain. - */ -static inline access_mask_t -init_layer_masks(const struct landlock_ruleset *const domain, - const access_mask_t access_request, - layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]) +static access_mask_t +get_handled_fs_accesses(const struct landlock_ruleset *const domain) { - access_mask_t handled_accesses = 0; - size_t layer_level; + /* Handles all initially denied by default access rights. */ + return get_raw_handled_fs_accesses(domain) | + LANDLOCK_ACCESS_FS_INITIALLY_DENIED; +} - memset(layer_masks, 0, sizeof(*layer_masks)); - /* An empty access request can happen because of O_WRONLY | O_RDWR. */ - if (!access_request) - return 0; +static const struct landlock_ruleset *get_current_fs_domain(void) +{ + const struct landlock_ruleset *const dom = + landlock_get_current_domain(); - /* Saves all handled accesses per layer. */ - for (layer_level = 0; layer_level < domain->num_layers; layer_level++) { - const unsigned long access_req = access_request; - unsigned long access_bit; + if (!dom || !get_raw_handled_fs_accesses(dom)) + return NULL; - for_each_set_bit(access_bit, &access_req, - ARRAY_SIZE(*layer_masks)) { - /* - * Artificially handles all initially denied by default - * access rights. - */ - if (BIT_ULL(access_bit) & - (domain->fs_access_masks[layer_level] | - ACCESS_INITIALLY_DENIED)) { - (*layer_masks)[access_bit] |= - BIT_ULL(layer_level); - handled_accesses |= BIT_ULL(access_bit); - } - } - } - return handled_accesses; + return dom; } /* @@ -519,7 +430,7 @@ static bool is_access_to_paths_allowed( * a superset of the meaningful requested accesses). */ access_masked_parent1 = access_masked_parent2 = - get_handled_accesses(domain); + get_handled_fs_accesses(domain); is_dom_check = true; } else { if (WARN_ON_ONCE(dentry_child1 || dentry_child2)) @@ -531,18 +442,22 @@ static bool is_access_to_paths_allowed( } if (unlikely(dentry_child1)) { - unmask_layers(find_rule(domain, dentry_child1), - init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS, - &_layer_masks_child1), - &_layer_masks_child1); + landlock_unmask_layers( + find_rule(domain, dentry_child1), + landlock_init_layer_masks( + domain, LANDLOCK_MASK_ACCESS_FS, + &_layer_masks_child1, LANDLOCK_KEY_INODE), + &_layer_masks_child1, ARRAY_SIZE(_layer_masks_child1)); layer_masks_child1 = &_layer_masks_child1; child1_is_directory = d_is_dir(dentry_child1); } if (unlikely(dentry_child2)) { - unmask_layers(find_rule(domain, dentry_child2), - init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS, - &_layer_masks_child2), - &_layer_masks_child2); + landlock_unmask_layers( + find_rule(domain, dentry_child2), + landlock_init_layer_masks( + domain, LANDLOCK_MASK_ACCESS_FS, + &_layer_masks_child2, LANDLOCK_KEY_INODE), + &_layer_masks_child2, ARRAY_SIZE(_layer_masks_child2)); layer_masks_child2 = &_layer_masks_child2; child2_is_directory = d_is_dir(dentry_child2); } @@ -594,15 +509,16 @@ static bool is_access_to_paths_allowed( } rule = find_rule(domain, walker_path.dentry); - allowed_parent1 = unmask_layers(rule, access_masked_parent1, - layer_masks_parent1); - allowed_parent2 = unmask_layers(rule, access_masked_parent2, - layer_masks_parent2); + allowed_parent1 = landlock_unmask_layers( + rule, access_masked_parent1, layer_masks_parent1, + ARRAY_SIZE(*layer_masks_parent1)); + allowed_parent2 = landlock_unmask_layers( + rule, access_masked_parent2, layer_masks_parent2, + ARRAY_SIZE(*layer_masks_parent2)); /* Stops when a rule from each layer grants access. */ if (allowed_parent1 && allowed_parent2) break; - jump_up: if (walker_path.dentry == walker_path.mnt->mnt_root) { if (follow_up(&walker_path)) { @@ -641,7 +557,8 @@ static inline int check_access_path(const struct landlock_ruleset *const domain, { layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {}; - access_request = init_layer_masks(domain, access_request, &layer_masks); + access_request = landlock_init_layer_masks( + domain, access_request, &layer_masks, LANDLOCK_KEY_INODE); if (is_access_to_paths_allowed(domain, path, access_request, &layer_masks, NULL, 0, NULL, NULL)) return 0; @@ -651,8 +568,7 @@ static inline int check_access_path(const struct landlock_ruleset *const domain, static inline int current_check_access_path(const struct path *const path, const access_mask_t access_request) { - const struct landlock_ruleset *const dom = - landlock_get_current_domain(); + const struct landlock_ruleset *const dom = get_current_fs_domain(); if (!dom) return 0; @@ -727,16 +643,18 @@ static bool collect_domain_accesses( if (is_nouser_or_private(dir)) return true; - access_dom = init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS, - layer_masks_dom); + access_dom = landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS, + layer_masks_dom, + LANDLOCK_KEY_INODE); dget(dir); while (true) { struct dentry *parent_dentry; /* Gets all layers allowing all domain accesses. */ - if (unmask_layers(find_rule(domain, dir), access_dom, - layer_masks_dom)) { + if (landlock_unmask_layers(find_rule(domain, dir), access_dom, + layer_masks_dom, + ARRAY_SIZE(*layer_masks_dom))) { /* * Stops when all handled accesses are allowed by at * least one rule in each layer. @@ -815,8 +733,7 @@ static int current_check_refer_path(struct dentry *const old_dentry, struct dentry *const new_dentry, const bool removable, const bool exchange) { - const struct landlock_ruleset *const dom = - landlock_get_current_domain(); + const struct landlock_ruleset *const dom = get_current_fs_domain(); bool allow_parent1, allow_parent2; access_mask_t access_request_parent1, access_request_parent2; struct path mnt_dir; @@ -850,9 +767,9 @@ static int current_check_refer_path(struct dentry *const old_dentry, * The LANDLOCK_ACCESS_FS_REFER access right is not required * for same-directory referer (i.e. no reparenting). */ - access_request_parent1 = init_layer_masks( + access_request_parent1 = landlock_init_layer_masks( dom, access_request_parent1 | access_request_parent2, - &layer_masks_parent1); + &layer_masks_parent1, LANDLOCK_KEY_INODE); if (is_access_to_paths_allowed( dom, new_dir, access_request_parent1, &layer_masks_parent1, NULL, 0, NULL, NULL)) @@ -1050,7 +967,7 @@ static int hook_sb_mount(const char *const dev_name, const struct path *const path, const char *const type, const unsigned long flags, void *const data) { - if (!landlock_get_current_domain()) + if (!get_current_fs_domain()) return 0; return -EPERM; } @@ -1058,7 +975,7 @@ static int hook_sb_mount(const char *const dev_name, static int hook_move_mount(const struct path *const from_path, const struct path *const to_path) { - if (!landlock_get_current_domain()) + if (!get_current_fs_domain()) return 0; return -EPERM; } @@ -1069,14 +986,14 @@ static int hook_move_mount(const struct path *const from_path, */ static int hook_sb_umount(struct vfsmount *const mnt, const int flags) { - if (!landlock_get_current_domain()) + if (!get_current_fs_domain()) return 0; return -EPERM; } static int hook_sb_remount(struct super_block *const sb, void *const mnt_opts) { - if (!landlock_get_current_domain()) + if (!get_current_fs_domain()) return 0; return -EPERM; } @@ -1092,7 +1009,7 @@ static int hook_sb_remount(struct super_block *const sb, void *const mnt_opts) static int hook_sb_pivotroot(const struct path *const old_path, const struct path *const new_path) { - if (!landlock_get_current_domain()) + if (!get_current_fs_domain()) return 0; return -EPERM; } @@ -1128,8 +1045,7 @@ static int hook_path_mknod(const struct path *const dir, struct dentry *const dentry, const umode_t mode, const unsigned int dev) { - const struct landlock_ruleset *const dom = - landlock_get_current_domain(); + const struct landlock_ruleset *const dom = get_current_fs_domain(); if (!dom) return 0; @@ -1208,8 +1124,7 @@ static int hook_file_open(struct file *const file) layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {}; access_mask_t open_access_request, full_access_request, allowed_access; const access_mask_t optional_access = LANDLOCK_ACCESS_FS_TRUNCATE; - const struct landlock_ruleset *const dom = - landlock_get_current_domain(); + const struct landlock_ruleset *const dom = get_current_fs_domain(); if (!dom) return 0; @@ -1229,7 +1144,8 @@ static int hook_file_open(struct file *const file) if (is_access_to_paths_allowed( dom, &file->f_path, - init_layer_masks(dom, full_access_request, &layer_masks), + landlock_init_layer_masks(dom, full_access_request, + &layer_masks, LANDLOCK_KEY_INODE), &layer_masks, NULL, 0, NULL, NULL)) { allowed_access = full_access_request; } else { diff --git a/security/landlock/limits.h b/security/landlock/limits.h index 82288f0e9e..93c9c6f915 100644 --- a/security/landlock/limits.h +++ b/security/landlock/limits.h @@ -21,6 +21,12 @@ #define LANDLOCK_LAST_ACCESS_FS LANDLOCK_ACCESS_FS_TRUNCATE #define LANDLOCK_MASK_ACCESS_FS ((LANDLOCK_LAST_ACCESS_FS << 1) - 1) #define LANDLOCK_NUM_ACCESS_FS __const_hweight64(LANDLOCK_MASK_ACCESS_FS) +#define LANDLOCK_SHIFT_ACCESS_FS 0 + +#define LANDLOCK_LAST_ACCESS_NET LANDLOCK_ACCESS_NET_CONNECT_TCP +#define LANDLOCK_MASK_ACCESS_NET ((LANDLOCK_LAST_ACCESS_NET << 1) - 1) +#define LANDLOCK_NUM_ACCESS_NET __const_hweight64(LANDLOCK_MASK_ACCESS_NET) +#define LANDLOCK_SHIFT_ACCESS_NET LANDLOCK_NUM_ACCESS_FS /* clang-format on */ diff --git a/security/landlock/net.c b/security/landlock/net.c new file mode 100644 index 0000000000..aaa92c2b1f --- /dev/null +++ b/security/landlock/net.c @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Landlock LSM - Network management and hooks + * + * Copyright © 2022-2023 Huawei Tech. Co., Ltd. + * Copyright © 2022-2023 Microsoft Corporation + */ + +#include <linux/in.h> +#include <linux/net.h> +#include <linux/socket.h> +#include <net/ipv6.h> + +#include "common.h" +#include "cred.h" +#include "limits.h" +#include "net.h" +#include "ruleset.h" + +int landlock_append_net_rule(struct landlock_ruleset *const ruleset, + const u16 port, access_mask_t access_rights) +{ + int err; + const struct landlock_id id = { + .key.data = (__force uintptr_t)htons(port), + .type = LANDLOCK_KEY_NET_PORT, + }; + + BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data)); + + /* Transforms relative access rights to absolute ones. */ + access_rights |= LANDLOCK_MASK_ACCESS_NET & + ~landlock_get_net_access_mask(ruleset, 0); + + mutex_lock(&ruleset->lock); + err = landlock_insert_rule(ruleset, id, access_rights); + mutex_unlock(&ruleset->lock); + + return err; +} + +static access_mask_t +get_raw_handled_net_accesses(const struct landlock_ruleset *const domain) +{ + access_mask_t access_dom = 0; + size_t layer_level; + + for (layer_level = 0; layer_level < domain->num_layers; layer_level++) + access_dom |= landlock_get_net_access_mask(domain, layer_level); + return access_dom; +} + +static const struct landlock_ruleset *get_current_net_domain(void) +{ + const struct landlock_ruleset *const dom = + landlock_get_current_domain(); + + if (!dom || !get_raw_handled_net_accesses(dom)) + return NULL; + + return dom; +} + +static int current_check_access_socket(struct socket *const sock, + struct sockaddr *const address, + const int addrlen, + const access_mask_t access_request) +{ + __be16 port; + layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {}; + const struct landlock_rule *rule; + access_mask_t handled_access; + struct landlock_id id = { + .type = LANDLOCK_KEY_NET_PORT, + }; + const struct landlock_ruleset *const dom = get_current_net_domain(); + + if (!dom) + return 0; + if (WARN_ON_ONCE(dom->num_layers < 1)) + return -EACCES; + + /* Checks if it's a (potential) TCP socket. */ + if (sock->type != SOCK_STREAM) + return 0; + + /* Checks for minimal header length to safely read sa_family. */ + if (addrlen < offsetofend(typeof(*address), sa_family)) + return -EINVAL; + + switch (address->sa_family) { + case AF_UNSPEC: + case AF_INET: + if (addrlen < sizeof(struct sockaddr_in)) + return -EINVAL; + port = ((struct sockaddr_in *)address)->sin_port; + break; + +#if IS_ENABLED(CONFIG_IPV6) + case AF_INET6: + if (addrlen < SIN6_LEN_RFC2133) + return -EINVAL; + port = ((struct sockaddr_in6 *)address)->sin6_port; + break; +#endif /* IS_ENABLED(CONFIG_IPV6) */ + + default: + return 0; + } + + /* Specific AF_UNSPEC handling. */ + if (address->sa_family == AF_UNSPEC) { + /* + * Connecting to an address with AF_UNSPEC dissolves the TCP + * association, which have the same effect as closing the + * connection while retaining the socket object (i.e., the file + * descriptor). As for dropping privileges, closing + * connections is always allowed. + * + * For a TCP access control system, this request is legitimate. + * Let the network stack handle potential inconsistencies and + * return -EINVAL if needed. + */ + if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP) + return 0; + + /* + * For compatibility reason, accept AF_UNSPEC for bind + * accesses (mapped to AF_INET) only if the address is + * INADDR_ANY (cf. __inet_bind). Checking the address is + * required to not wrongfully return -EACCES instead of + * -EAFNOSUPPORT. + * + * We could return 0 and let the network stack handle these + * checks, but it is safer to return a proper error and test + * consistency thanks to kselftest. + */ + if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) { + /* addrlen has already been checked for AF_UNSPEC. */ + const struct sockaddr_in *const sockaddr = + (struct sockaddr_in *)address; + + if (sock->sk->__sk_common.skc_family != AF_INET) + return -EINVAL; + + if (sockaddr->sin_addr.s_addr != htonl(INADDR_ANY)) + return -EAFNOSUPPORT; + } + } else { + /* + * Checks sa_family consistency to not wrongfully return + * -EACCES instead of -EINVAL. Valid sa_family changes are + * only (from AF_INET or AF_INET6) to AF_UNSPEC. + * + * We could return 0 and let the network stack handle this + * check, but it is safer to return a proper error and test + * consistency thanks to kselftest. + */ + if (address->sa_family != sock->sk->__sk_common.skc_family) + return -EINVAL; + } + + id.key.data = (__force uintptr_t)port; + BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data)); + + rule = landlock_find_rule(dom, id); + handled_access = landlock_init_layer_masks( + dom, access_request, &layer_masks, LANDLOCK_KEY_NET_PORT); + if (landlock_unmask_layers(rule, handled_access, &layer_masks, + ARRAY_SIZE(layer_masks))) + return 0; + + return -EACCES; +} + +static int hook_socket_bind(struct socket *const sock, + struct sockaddr *const address, const int addrlen) +{ + return current_check_access_socket(sock, address, addrlen, + LANDLOCK_ACCESS_NET_BIND_TCP); +} + +static int hook_socket_connect(struct socket *const sock, + struct sockaddr *const address, + const int addrlen) +{ + return current_check_access_socket(sock, address, addrlen, + LANDLOCK_ACCESS_NET_CONNECT_TCP); +} + +static struct security_hook_list landlock_hooks[] __ro_after_init = { + LSM_HOOK_INIT(socket_bind, hook_socket_bind), + LSM_HOOK_INIT(socket_connect, hook_socket_connect), +}; + +__init void landlock_add_net_hooks(void) +{ + security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks), + LANDLOCK_NAME); +} diff --git a/security/landlock/net.h b/security/landlock/net.h new file mode 100644 index 0000000000..09960c237a --- /dev/null +++ b/security/landlock/net.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Landlock LSM - Network management and hooks + * + * Copyright © 2022-2023 Huawei Tech. Co., Ltd. + */ + +#ifndef _SECURITY_LANDLOCK_NET_H +#define _SECURITY_LANDLOCK_NET_H + +#include "common.h" +#include "ruleset.h" +#include "setup.h" + +#if IS_ENABLED(CONFIG_INET) +__init void landlock_add_net_hooks(void); + +int landlock_append_net_rule(struct landlock_ruleset *const ruleset, + const u16 port, access_mask_t access_rights); +#else /* IS_ENABLED(CONFIG_INET) */ +static inline void landlock_add_net_hooks(void) +{ +} + +static inline int +landlock_append_net_rule(struct landlock_ruleset *const ruleset, const u16 port, + access_mask_t access_rights) +{ + return -EAFNOSUPPORT; +} +#endif /* IS_ENABLED(CONFIG_INET) */ + +#endif /* _SECURITY_LANDLOCK_NET_H */ diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c index 996484f98b..ffedc99f2b 100644 --- a/security/landlock/ruleset.c +++ b/security/landlock/ruleset.c @@ -29,33 +29,43 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers) struct landlock_ruleset *new_ruleset; new_ruleset = - kzalloc(struct_size(new_ruleset, fs_access_masks, num_layers), + kzalloc(struct_size(new_ruleset, access_masks, num_layers), GFP_KERNEL_ACCOUNT); if (!new_ruleset) return ERR_PTR(-ENOMEM); refcount_set(&new_ruleset->usage, 1); mutex_init(&new_ruleset->lock); - new_ruleset->root = RB_ROOT; + new_ruleset->root_inode = RB_ROOT; + +#if IS_ENABLED(CONFIG_INET) + new_ruleset->root_net_port = RB_ROOT; +#endif /* IS_ENABLED(CONFIG_INET) */ + new_ruleset->num_layers = num_layers; /* * hierarchy = NULL * num_rules = 0 - * fs_access_masks[] = 0 + * access_masks[] = 0 */ return new_ruleset; } struct landlock_ruleset * -landlock_create_ruleset(const access_mask_t fs_access_mask) +landlock_create_ruleset(const access_mask_t fs_access_mask, + const access_mask_t net_access_mask) { struct landlock_ruleset *new_ruleset; /* Informs about useless ruleset. */ - if (!fs_access_mask) + if (!fs_access_mask && !net_access_mask) return ERR_PTR(-ENOMSG); new_ruleset = create_ruleset(1); - if (!IS_ERR(new_ruleset)) - new_ruleset->fs_access_masks[0] = fs_access_mask; + if (IS_ERR(new_ruleset)) + return new_ruleset; + if (fs_access_mask) + landlock_add_fs_access_mask(new_ruleset, fs_access_mask, 0); + if (net_access_mask) + landlock_add_net_access_mask(new_ruleset, net_access_mask, 0); return new_ruleset; } @@ -68,8 +78,25 @@ static void build_check_rule(void) BUILD_BUG_ON(rule.num_layers < LANDLOCK_MAX_NUM_LAYERS); } +static bool is_object_pointer(const enum landlock_key_type key_type) +{ + switch (key_type) { + case LANDLOCK_KEY_INODE: + return true; + +#if IS_ENABLED(CONFIG_INET) + case LANDLOCK_KEY_NET_PORT: + return false; +#endif /* IS_ENABLED(CONFIG_INET) */ + + default: + WARN_ON_ONCE(1); + return false; + } +} + static struct landlock_rule * -create_rule(struct landlock_object *const object, +create_rule(const struct landlock_id id, const struct landlock_layer (*const layers)[], const u32 num_layers, const struct landlock_layer *const new_layer) { @@ -90,8 +117,13 @@ create_rule(struct landlock_object *const object, if (!new_rule) return ERR_PTR(-ENOMEM); RB_CLEAR_NODE(&new_rule->node); - landlock_get_object(object); - new_rule->object = object; + if (is_object_pointer(id.type)) { + /* This should be catched by insert_rule(). */ + WARN_ON_ONCE(!id.key.object); + landlock_get_object(id.key.object); + } + + new_rule->key = id.key; new_rule->num_layers = new_num_layers; /* Copies the original layer stack. */ memcpy(new_rule->layers, layers, @@ -102,12 +134,32 @@ create_rule(struct landlock_object *const object, return new_rule; } -static void free_rule(struct landlock_rule *const rule) +static struct rb_root *get_root(struct landlock_ruleset *const ruleset, + const enum landlock_key_type key_type) +{ + switch (key_type) { + case LANDLOCK_KEY_INODE: + return &ruleset->root_inode; + +#if IS_ENABLED(CONFIG_INET) + case LANDLOCK_KEY_NET_PORT: + return &ruleset->root_net_port; +#endif /* IS_ENABLED(CONFIG_INET) */ + + default: + WARN_ON_ONCE(1); + return ERR_PTR(-EINVAL); + } +} + +static void free_rule(struct landlock_rule *const rule, + const enum landlock_key_type key_type) { might_sleep(); if (!rule) return; - landlock_put_object(rule->object); + if (is_object_pointer(key_type)) + landlock_put_object(rule->key.object); kfree(rule); } @@ -117,19 +169,21 @@ static void build_check_ruleset(void) .num_rules = ~0, .num_layers = ~0, }; - typeof(ruleset.fs_access_masks[0]) fs_access_mask = ~0; + typeof(ruleset.access_masks[0]) access_masks = ~0; BUILD_BUG_ON(ruleset.num_rules < LANDLOCK_MAX_NUM_RULES); BUILD_BUG_ON(ruleset.num_layers < LANDLOCK_MAX_NUM_LAYERS); - BUILD_BUG_ON(fs_access_mask < LANDLOCK_MASK_ACCESS_FS); + BUILD_BUG_ON(access_masks < + ((LANDLOCK_MASK_ACCESS_FS << LANDLOCK_SHIFT_ACCESS_FS) | + (LANDLOCK_MASK_ACCESS_NET << LANDLOCK_SHIFT_ACCESS_NET))); } /** * insert_rule - Create and insert a rule in a ruleset * * @ruleset: The ruleset to be updated. - * @object: The object to build the new rule with. The underlying kernel - * object must be held by the caller. + * @id: The ID to build the new rule with. The underlying kernel object, if + * any, must be held by the caller. * @layers: One or multiple layers to be copied into the new rule. * @num_layers: The number of @layers entries. * @@ -143,26 +197,35 @@ static void build_check_ruleset(void) * access rights. */ static int insert_rule(struct landlock_ruleset *const ruleset, - struct landlock_object *const object, + const struct landlock_id id, const struct landlock_layer (*const layers)[], - size_t num_layers) + const size_t num_layers) { struct rb_node **walker_node; struct rb_node *parent_node = NULL; struct landlock_rule *new_rule; + struct rb_root *root; might_sleep(); lockdep_assert_held(&ruleset->lock); - if (WARN_ON_ONCE(!object || !layers)) + if (WARN_ON_ONCE(!layers)) return -ENOENT; - walker_node = &(ruleset->root.rb_node); + + if (is_object_pointer(id.type) && WARN_ON_ONCE(!id.key.object)) + return -ENOENT; + + root = get_root(ruleset, id.type); + if (IS_ERR(root)) + return PTR_ERR(root); + + walker_node = &root->rb_node; while (*walker_node) { struct landlock_rule *const this = rb_entry(*walker_node, struct landlock_rule, node); - if (this->object != object) { + if (this->key.data != id.key.data) { parent_node = *walker_node; - if (this->object < object) + if (this->key.data < id.key.data) walker_node = &((*walker_node)->rb_right); else walker_node = &((*walker_node)->rb_left); @@ -194,24 +257,24 @@ static int insert_rule(struct landlock_ruleset *const ruleset, * Intersects access rights when it is a merge between a * ruleset and a domain. */ - new_rule = create_rule(object, &this->layers, this->num_layers, + new_rule = create_rule(id, &this->layers, this->num_layers, &(*layers)[0]); if (IS_ERR(new_rule)) return PTR_ERR(new_rule); - rb_replace_node(&this->node, &new_rule->node, &ruleset->root); - free_rule(this); + rb_replace_node(&this->node, &new_rule->node, root); + free_rule(this, id.type); return 0; } - /* There is no match for @object. */ + /* There is no match for @id. */ build_check_ruleset(); if (ruleset->num_rules >= LANDLOCK_MAX_NUM_RULES) return -E2BIG; - new_rule = create_rule(object, layers, num_layers, NULL); + new_rule = create_rule(id, layers, num_layers, NULL); if (IS_ERR(new_rule)) return PTR_ERR(new_rule); rb_link_node(&new_rule->node, parent_node, walker_node); - rb_insert_color(&new_rule->node, &ruleset->root); + rb_insert_color(&new_rule->node, root); ruleset->num_rules++; return 0; } @@ -229,7 +292,7 @@ static void build_check_layer(void) /* @ruleset must be locked by the caller. */ int landlock_insert_rule(struct landlock_ruleset *const ruleset, - struct landlock_object *const object, + const struct landlock_id id, const access_mask_t access) { struct landlock_layer layers[] = { { @@ -239,7 +302,7 @@ int landlock_insert_rule(struct landlock_ruleset *const ruleset, } }; build_check_layer(); - return insert_rule(ruleset, object, &layers, ARRAY_SIZE(layers)); + return insert_rule(ruleset, id, &layers, ARRAY_SIZE(layers)); } static inline void get_hierarchy(struct landlock_hierarchy *const hierarchy) @@ -258,10 +321,51 @@ static void put_hierarchy(struct landlock_hierarchy *hierarchy) } } +static int merge_tree(struct landlock_ruleset *const dst, + struct landlock_ruleset *const src, + const enum landlock_key_type key_type) +{ + struct landlock_rule *walker_rule, *next_rule; + struct rb_root *src_root; + int err = 0; + + might_sleep(); + lockdep_assert_held(&dst->lock); + lockdep_assert_held(&src->lock); + + src_root = get_root(src, key_type); + if (IS_ERR(src_root)) + return PTR_ERR(src_root); + + /* Merges the @src tree. */ + rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, src_root, + node) { + struct landlock_layer layers[] = { { + .level = dst->num_layers, + } }; + const struct landlock_id id = { + .key = walker_rule->key, + .type = key_type, + }; + + if (WARN_ON_ONCE(walker_rule->num_layers != 1)) + return -EINVAL; + + if (WARN_ON_ONCE(walker_rule->layers[0].level != 0)) + return -EINVAL; + + layers[0].access = walker_rule->layers[0].access; + + err = insert_rule(dst, id, &layers, ARRAY_SIZE(layers)); + if (err) + return err; + } + return err; +} + static int merge_ruleset(struct landlock_ruleset *const dst, struct landlock_ruleset *const src) { - struct landlock_rule *walker_rule, *next_rule; int err = 0; might_sleep(); @@ -281,29 +385,19 @@ static int merge_ruleset(struct landlock_ruleset *const dst, err = -EINVAL; goto out_unlock; } - dst->fs_access_masks[dst->num_layers - 1] = src->fs_access_masks[0]; + dst->access_masks[dst->num_layers - 1] = src->access_masks[0]; - /* Merges the @src tree. */ - rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, &src->root, - node) { - struct landlock_layer layers[] = { { - .level = dst->num_layers, - } }; + /* Merges the @src inode tree. */ + err = merge_tree(dst, src, LANDLOCK_KEY_INODE); + if (err) + goto out_unlock; - if (WARN_ON_ONCE(walker_rule->num_layers != 1)) { - err = -EINVAL; - goto out_unlock; - } - if (WARN_ON_ONCE(walker_rule->layers[0].level != 0)) { - err = -EINVAL; - goto out_unlock; - } - layers[0].access = walker_rule->layers[0].access; - err = insert_rule(dst, walker_rule->object, &layers, - ARRAY_SIZE(layers)); - if (err) - goto out_unlock; - } +#if IS_ENABLED(CONFIG_INET) + /* Merges the @src network port tree. */ + err = merge_tree(dst, src, LANDLOCK_KEY_NET_PORT); + if (err) + goto out_unlock; +#endif /* IS_ENABLED(CONFIG_INET) */ out_unlock: mutex_unlock(&src->lock); @@ -311,10 +405,41 @@ out_unlock: return err; } +static int inherit_tree(struct landlock_ruleset *const parent, + struct landlock_ruleset *const child, + const enum landlock_key_type key_type) +{ + struct landlock_rule *walker_rule, *next_rule; + struct rb_root *parent_root; + int err = 0; + + might_sleep(); + lockdep_assert_held(&parent->lock); + lockdep_assert_held(&child->lock); + + parent_root = get_root(parent, key_type); + if (IS_ERR(parent_root)) + return PTR_ERR(parent_root); + + /* Copies the @parent inode or network tree. */ + rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, + parent_root, node) { + const struct landlock_id id = { + .key = walker_rule->key, + .type = key_type, + }; + + err = insert_rule(child, id, &walker_rule->layers, + walker_rule->num_layers); + if (err) + return err; + } + return err; +} + static int inherit_ruleset(struct landlock_ruleset *const parent, struct landlock_ruleset *const child) { - struct landlock_rule *walker_rule, *next_rule; int err = 0; might_sleep(); @@ -325,23 +450,25 @@ static int inherit_ruleset(struct landlock_ruleset *const parent, mutex_lock(&child->lock); mutex_lock_nested(&parent->lock, SINGLE_DEPTH_NESTING); - /* Copies the @parent tree. */ - rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, - &parent->root, node) { - err = insert_rule(child, walker_rule->object, - &walker_rule->layers, - walker_rule->num_layers); - if (err) - goto out_unlock; - } + /* Copies the @parent inode tree. */ + err = inherit_tree(parent, child, LANDLOCK_KEY_INODE); + if (err) + goto out_unlock; + +#if IS_ENABLED(CONFIG_INET) + /* Copies the @parent network port tree. */ + err = inherit_tree(parent, child, LANDLOCK_KEY_NET_PORT); + if (err) + goto out_unlock; +#endif /* IS_ENABLED(CONFIG_INET) */ if (WARN_ON_ONCE(child->num_layers <= parent->num_layers)) { err = -EINVAL; goto out_unlock; } /* Copies the parent layer stack and leaves a space for the new layer. */ - memcpy(child->fs_access_masks, parent->fs_access_masks, - flex_array_size(parent, fs_access_masks, parent->num_layers)); + memcpy(child->access_masks, parent->access_masks, + flex_array_size(parent, access_masks, parent->num_layers)); if (WARN_ON_ONCE(!parent->hierarchy)) { err = -EINVAL; @@ -361,8 +488,16 @@ static void free_ruleset(struct landlock_ruleset *const ruleset) struct landlock_rule *freeme, *next; might_sleep(); - rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root, node) - free_rule(freeme); + rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root_inode, + node) + free_rule(freeme, LANDLOCK_KEY_INODE); + +#if IS_ENABLED(CONFIG_INET) + rbtree_postorder_for_each_entry_safe(freeme, next, + &ruleset->root_net_port, node) + free_rule(freeme, LANDLOCK_KEY_NET_PORT); +#endif /* IS_ENABLED(CONFIG_INET) */ + put_hierarchy(ruleset->hierarchy); kfree(ruleset); } @@ -453,23 +588,151 @@ out_put_dom: */ const struct landlock_rule * landlock_find_rule(const struct landlock_ruleset *const ruleset, - const struct landlock_object *const object) + const struct landlock_id id) { + const struct rb_root *root; const struct rb_node *node; - if (!object) + root = get_root((struct landlock_ruleset *)ruleset, id.type); + if (IS_ERR(root)) return NULL; - node = ruleset->root.rb_node; + node = root->rb_node; + while (node) { struct landlock_rule *this = rb_entry(node, struct landlock_rule, node); - if (this->object == object) + if (this->key.data == id.key.data) return this; - if (this->object < object) + if (this->key.data < id.key.data) node = node->rb_right; else node = node->rb_left; } return NULL; } + +/* + * @layer_masks is read and may be updated according to the access request and + * the matching rule. + * @masks_array_size must be equal to ARRAY_SIZE(*layer_masks). + * + * Returns true if the request is allowed (i.e. relevant layer masks for the + * request are empty). + */ +bool landlock_unmask_layers(const struct landlock_rule *const rule, + const access_mask_t access_request, + layer_mask_t (*const layer_masks)[], + const size_t masks_array_size) +{ + size_t layer_level; + + if (!access_request || !layer_masks) + return true; + if (!rule) + return false; + + /* + * An access is granted if, for each policy layer, at least one rule + * encountered on the pathwalk grants the requested access, + * regardless of its position in the layer stack. We must then check + * the remaining layers for each inode, from the first added layer to + * the last one. When there is multiple requested accesses, for each + * policy layer, the full set of requested accesses may not be granted + * by only one rule, but by the union (binary OR) of multiple rules. + * E.g. /a/b <execute> + /a <read> => /a/b <execute + read> + */ + for (layer_level = 0; layer_level < rule->num_layers; layer_level++) { + const struct landlock_layer *const layer = + &rule->layers[layer_level]; + const layer_mask_t layer_bit = BIT_ULL(layer->level - 1); + const unsigned long access_req = access_request; + unsigned long access_bit; + bool is_empty; + + /* + * Records in @layer_masks which layer grants access to each + * requested access. + */ + is_empty = true; + for_each_set_bit(access_bit, &access_req, masks_array_size) { + if (layer->access & BIT_ULL(access_bit)) + (*layer_masks)[access_bit] &= ~layer_bit; + is_empty = is_empty && !(*layer_masks)[access_bit]; + } + if (is_empty) + return true; + } + return false; +} + +typedef access_mask_t +get_access_mask_t(const struct landlock_ruleset *const ruleset, + const u16 layer_level); + +/** + * landlock_init_layer_masks - Initialize layer masks from an access request + * + * Populates @layer_masks such that for each access right in @access_request, + * the bits for all the layers are set where this access right is handled. + * + * @domain: The domain that defines the current restrictions. + * @access_request: The requested access rights to check. + * @layer_masks: It must contain %LANDLOCK_NUM_ACCESS_FS or + * %LANDLOCK_NUM_ACCESS_NET elements according to @key_type. + * @key_type: The key type to switch between access masks of different types. + * + * Returns: An access mask where each access right bit is set which is handled + * in any of the active layers in @domain. + */ +access_mask_t +landlock_init_layer_masks(const struct landlock_ruleset *const domain, + const access_mask_t access_request, + layer_mask_t (*const layer_masks)[], + const enum landlock_key_type key_type) +{ + access_mask_t handled_accesses = 0; + size_t layer_level, num_access; + get_access_mask_t *get_access_mask; + + switch (key_type) { + case LANDLOCK_KEY_INODE: + get_access_mask = landlock_get_fs_access_mask; + num_access = LANDLOCK_NUM_ACCESS_FS; + break; + +#if IS_ENABLED(CONFIG_INET) + case LANDLOCK_KEY_NET_PORT: + get_access_mask = landlock_get_net_access_mask; + num_access = LANDLOCK_NUM_ACCESS_NET; + break; +#endif /* IS_ENABLED(CONFIG_INET) */ + + default: + WARN_ON_ONCE(1); + return 0; + } + + memset(layer_masks, 0, + array_size(sizeof((*layer_masks)[0]), num_access)); + + /* An empty access request can happen because of O_WRONLY | O_RDWR. */ + if (!access_request) + return 0; + + /* Saves all handled accesses per layer. */ + for (layer_level = 0; layer_level < domain->num_layers; layer_level++) { + const unsigned long access_req = access_request; + unsigned long access_bit; + + for_each_set_bit(access_bit, &access_req, num_access) { + if (BIT_ULL(access_bit) & + get_access_mask(domain, layer_level)) { + (*layer_masks)[access_bit] |= + BIT_ULL(layer_level); + handled_accesses |= BIT_ULL(access_bit); + } + } + } + return handled_accesses; +} diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h index 55b1df8f66..c7f1526784 100644 --- a/security/landlock/ruleset.h +++ b/security/landlock/ruleset.h @@ -15,16 +15,35 @@ #include <linux/rbtree.h> #include <linux/refcount.h> #include <linux/workqueue.h> +#include <uapi/linux/landlock.h> #include "limits.h" #include "object.h" +/* + * All access rights that are denied by default whether they are handled or not + * by a ruleset/layer. This must be ORed with all ruleset->access_masks[] + * entries when we need to get the absolute handled access masks. + */ +/* clang-format off */ +#define LANDLOCK_ACCESS_FS_INITIALLY_DENIED ( \ + LANDLOCK_ACCESS_FS_REFER) +/* clang-format on */ + typedef u16 access_mask_t; /* Makes sure all filesystem access rights can be stored. */ static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS); +/* Makes sure all network access rights can be stored. */ +static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_NET); /* Makes sure for_each_set_bit() and for_each_clear_bit() calls are OK. */ static_assert(sizeof(unsigned long) >= sizeof(access_mask_t)); +/* Ruleset access masks. */ +typedef u32 access_masks_t; +/* Makes sure all ruleset access rights can be stored. */ +static_assert(BITS_PER_TYPE(access_masks_t) >= + LANDLOCK_NUM_ACCESS_FS + LANDLOCK_NUM_ACCESS_NET); + typedef u16 layer_mask_t; /* Makes sure all layers can be checked. */ static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS); @@ -45,6 +64,52 @@ struct landlock_layer { }; /** + * union landlock_key - Key of a ruleset's red-black tree + */ +union landlock_key { + /** + * @object: Pointer to identify a kernel object (e.g. an inode). + */ + struct landlock_object *object; + /** + * @data: Raw data to identify an arbitrary 32-bit value + * (e.g. a TCP port). + */ + uintptr_t data; +}; + +/** + * enum landlock_key_type - Type of &union landlock_key + */ +enum landlock_key_type { + /** + * @LANDLOCK_KEY_INODE: Type of &landlock_ruleset.root_inode's node + * keys. + */ + LANDLOCK_KEY_INODE = 1, + /** + * @LANDLOCK_KEY_NET_PORT: Type of &landlock_ruleset.root_net_port's + * node keys. + */ + LANDLOCK_KEY_NET_PORT, +}; + +/** + * struct landlock_id - Unique rule identifier for a ruleset + */ +struct landlock_id { + /** + * @key: Identifies either a kernel object (e.g. an inode) or + * a raw value (e.g. a TCP port). + */ + union landlock_key key; + /** + * @type: Type of a landlock_ruleset's root tree. + */ + const enum landlock_key_type type; +}; + +/** * struct landlock_rule - Access rights tied to an object */ struct landlock_rule { @@ -53,12 +118,13 @@ struct landlock_rule { */ struct rb_node node; /** - * @object: Pointer to identify a kernel object (e.g. an inode). This - * is used as a key for this ruleset element. This pointer is set once - * and never modified. It always points to an allocated object because - * each rule increments the refcount of its object. + * @key: A union to identify either a kernel object (e.g. an inode) or + * a raw data value (e.g. a network socket port). This is used as a key + * for this ruleset element. The pointer is set once and never + * modified. It always points to an allocated object because each rule + * increments the refcount of its object. */ - struct landlock_object *object; + union landlock_key key; /** * @num_layers: Number of entries in @layers. */ @@ -94,11 +160,23 @@ struct landlock_hierarchy { */ struct landlock_ruleset { /** - * @root: Root of a red-black tree containing &struct landlock_rule - * nodes. Once a ruleset is tied to a process (i.e. as a domain), this - * tree is immutable until @usage reaches zero. + * @root_inode: Root of a red-black tree containing &struct + * landlock_rule nodes with inode object. Once a ruleset is tied to a + * process (i.e. as a domain), this tree is immutable until @usage + * reaches zero. */ - struct rb_root root; + struct rb_root root_inode; + +#if IS_ENABLED(CONFIG_INET) + /** + * @root_net_port: Root of a red-black tree containing &struct + * landlock_rule nodes with network port. Once a ruleset is tied to a + * process (i.e. as a domain), this tree is immutable until @usage + * reaches zero. + */ + struct rb_root root_net_port; +#endif /* IS_ENABLED(CONFIG_INET) */ + /** * @hierarchy: Enables hierarchy identification even when a parent * domain vanishes. This is needed for the ptrace protection. @@ -110,7 +188,7 @@ struct landlock_ruleset { * section. This is only used by * landlock_put_ruleset_deferred() when @usage reaches zero. * The fields @lock, @usage, @num_rules, @num_layers and - * @fs_access_masks are then unused. + * @access_masks are then unused. */ struct work_struct work_free; struct { @@ -137,30 +215,31 @@ struct landlock_ruleset { */ u32 num_layers; /** - * @fs_access_masks: Contains the subset of filesystem - * actions that are restricted by a ruleset. A domain - * saves all layers of merged rulesets in a stack - * (FAM), starting from the first layer to the last - * one. These layers are used when merging rulesets, - * for user space backward compatibility (i.e. - * future-proof), and to properly handle merged + * @access_masks: Contains the subset of filesystem and + * network actions that are restricted by a ruleset. + * A domain saves all layers of merged rulesets in a + * stack (FAM), starting from the first layer to the + * last one. These layers are used when merging + * rulesets, for user space backward compatibility + * (i.e. future-proof), and to properly handle merged * rulesets without overlapping access rights. These * layers are set once and never changed for the * lifetime of the ruleset. */ - access_mask_t fs_access_masks[]; + access_masks_t access_masks[]; }; }; }; struct landlock_ruleset * -landlock_create_ruleset(const access_mask_t fs_access_mask); +landlock_create_ruleset(const access_mask_t access_mask_fs, + const access_mask_t access_mask_net); void landlock_put_ruleset(struct landlock_ruleset *const ruleset); void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset); int landlock_insert_rule(struct landlock_ruleset *const ruleset, - struct landlock_object *const object, + const struct landlock_id id, const access_mask_t access); struct landlock_ruleset * @@ -169,7 +248,7 @@ landlock_merge_ruleset(struct landlock_ruleset *const parent, const struct landlock_rule * landlock_find_rule(const struct landlock_ruleset *const ruleset, - const struct landlock_object *const object); + const struct landlock_id id); static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset) { @@ -177,4 +256,68 @@ static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset) refcount_inc(&ruleset->usage); } +static inline void +landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset, + const access_mask_t fs_access_mask, + const u16 layer_level) +{ + access_mask_t fs_mask = fs_access_mask & LANDLOCK_MASK_ACCESS_FS; + + /* Should already be checked in sys_landlock_create_ruleset(). */ + WARN_ON_ONCE(fs_access_mask != fs_mask); + ruleset->access_masks[layer_level] |= + (fs_mask << LANDLOCK_SHIFT_ACCESS_FS); +} + +static inline void +landlock_add_net_access_mask(struct landlock_ruleset *const ruleset, + const access_mask_t net_access_mask, + const u16 layer_level) +{ + access_mask_t net_mask = net_access_mask & LANDLOCK_MASK_ACCESS_NET; + + /* Should already be checked in sys_landlock_create_ruleset(). */ + WARN_ON_ONCE(net_access_mask != net_mask); + ruleset->access_masks[layer_level] |= + (net_mask << LANDLOCK_SHIFT_ACCESS_NET); +} + +static inline access_mask_t +landlock_get_raw_fs_access_mask(const struct landlock_ruleset *const ruleset, + const u16 layer_level) +{ + return (ruleset->access_masks[layer_level] >> + LANDLOCK_SHIFT_ACCESS_FS) & + LANDLOCK_MASK_ACCESS_FS; +} + +static inline access_mask_t +landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset, + const u16 layer_level) +{ + /* Handles all initially denied by default access rights. */ + return landlock_get_raw_fs_access_mask(ruleset, layer_level) | + LANDLOCK_ACCESS_FS_INITIALLY_DENIED; +} + +static inline access_mask_t +landlock_get_net_access_mask(const struct landlock_ruleset *const ruleset, + const u16 layer_level) +{ + return (ruleset->access_masks[layer_level] >> + LANDLOCK_SHIFT_ACCESS_NET) & + LANDLOCK_MASK_ACCESS_NET; +} + +bool landlock_unmask_layers(const struct landlock_rule *const rule, + const access_mask_t access_request, + layer_mask_t (*const layer_masks)[], + const size_t masks_array_size); + +access_mask_t +landlock_init_layer_masks(const struct landlock_ruleset *const domain, + const access_mask_t access_request, + layer_mask_t (*const layer_masks)[], + const enum landlock_key_type key_type); + #endif /* _SECURITY_LANDLOCK_RULESET_H */ diff --git a/security/landlock/setup.c b/security/landlock/setup.c index 0f6113528f..3e11d30354 100644 --- a/security/landlock/setup.c +++ b/security/landlock/setup.c @@ -12,6 +12,7 @@ #include "common.h" #include "cred.h" #include "fs.h" +#include "net.h" #include "ptrace.h" #include "setup.h" @@ -29,6 +30,7 @@ static int __init landlock_init(void) landlock_add_cred_hooks(); landlock_add_ptrace_hooks(); landlock_add_fs_hooks(); + landlock_add_net_hooks(); landlock_initialized = true; pr_info("Up and running.\n"); return 0; diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index 245cc650a4..898358f57f 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -29,6 +29,7 @@ #include "cred.h" #include "fs.h" #include "limits.h" +#include "net.h" #include "ruleset.h" #include "setup.h" @@ -74,7 +75,8 @@ static void build_check_abi(void) { struct landlock_ruleset_attr ruleset_attr; struct landlock_path_beneath_attr path_beneath_attr; - size_t ruleset_size, path_beneath_size; + struct landlock_net_port_attr net_port_attr; + size_t ruleset_size, path_beneath_size, net_port_size; /* * For each user space ABI structures, first checks that there is no @@ -82,13 +84,19 @@ static void build_check_abi(void) * struct size. */ ruleset_size = sizeof(ruleset_attr.handled_access_fs); + ruleset_size += sizeof(ruleset_attr.handled_access_net); BUILD_BUG_ON(sizeof(ruleset_attr) != ruleset_size); - BUILD_BUG_ON(sizeof(ruleset_attr) != 8); + BUILD_BUG_ON(sizeof(ruleset_attr) != 16); path_beneath_size = sizeof(path_beneath_attr.allowed_access); path_beneath_size += sizeof(path_beneath_attr.parent_fd); BUILD_BUG_ON(sizeof(path_beneath_attr) != path_beneath_size); BUILD_BUG_ON(sizeof(path_beneath_attr) != 12); + + net_port_size = sizeof(net_port_attr.allowed_access); + net_port_size += sizeof(net_port_attr.port); + BUILD_BUG_ON(sizeof(net_port_attr) != net_port_size); + BUILD_BUG_ON(sizeof(net_port_attr) != 16); } /* Ruleset handling */ @@ -129,7 +137,7 @@ static const struct file_operations ruleset_fops = { .write = fop_dummy_write, }; -#define LANDLOCK_ABI_VERSION 3 +#define LANDLOCK_ABI_VERSION 4 /** * sys_landlock_create_ruleset - Create a new ruleset @@ -188,8 +196,14 @@ SYSCALL_DEFINE3(landlock_create_ruleset, LANDLOCK_MASK_ACCESS_FS) return -EINVAL; + /* Checks network content (and 32-bits cast). */ + if ((ruleset_attr.handled_access_net | LANDLOCK_MASK_ACCESS_NET) != + LANDLOCK_MASK_ACCESS_NET) + return -EINVAL; + /* Checks arguments and transforms to kernel struct. */ - ruleset = landlock_create_ruleset(ruleset_attr.handled_access_fs); + ruleset = landlock_create_ruleset(ruleset_attr.handled_access_fs, + ruleset_attr.handled_access_net); if (IS_ERR(ruleset)) return PTR_ERR(ruleset); @@ -274,13 +288,84 @@ out_fdput: return err; } +static int add_rule_path_beneath(struct landlock_ruleset *const ruleset, + const void __user *const rule_attr) +{ + struct landlock_path_beneath_attr path_beneath_attr; + struct path path; + int res, err; + access_mask_t mask; + + /* Copies raw user space buffer. */ + res = copy_from_user(&path_beneath_attr, rule_attr, + sizeof(path_beneath_attr)); + if (res) + return -EFAULT; + + /* + * Informs about useless rule: empty allowed_access (i.e. deny rules) + * are ignored in path walks. + */ + if (!path_beneath_attr.allowed_access) + return -ENOMSG; + + /* Checks that allowed_access matches the @ruleset constraints. */ + mask = landlock_get_raw_fs_access_mask(ruleset, 0); + if ((path_beneath_attr.allowed_access | mask) != mask) + return -EINVAL; + + /* Gets and checks the new rule. */ + err = get_path_from_fd(path_beneath_attr.parent_fd, &path); + if (err) + return err; + + /* Imports the new rule. */ + err = landlock_append_fs_rule(ruleset, &path, + path_beneath_attr.allowed_access); + path_put(&path); + return err; +} + +static int add_rule_net_port(struct landlock_ruleset *ruleset, + const void __user *const rule_attr) +{ + struct landlock_net_port_attr net_port_attr; + int res; + access_mask_t mask; + + /* Copies raw user space buffer. */ + res = copy_from_user(&net_port_attr, rule_attr, sizeof(net_port_attr)); + if (res) + return -EFAULT; + + /* + * Informs about useless rule: empty allowed_access (i.e. deny rules) + * are ignored by network actions. + */ + if (!net_port_attr.allowed_access) + return -ENOMSG; + + /* Checks that allowed_access matches the @ruleset constraints. */ + mask = landlock_get_net_access_mask(ruleset, 0); + if ((net_port_attr.allowed_access | mask) != mask) + return -EINVAL; + + /* Denies inserting a rule with port greater than 65535. */ + if (net_port_attr.port > U16_MAX) + return -EINVAL; + + /* Imports the new rule. */ + return landlock_append_net_rule(ruleset, net_port_attr.port, + net_port_attr.allowed_access); +} + /** * sys_landlock_add_rule - Add a new rule to a ruleset * * @ruleset_fd: File descriptor tied to the ruleset that should be extended * with the new rule. - * @rule_type: Identify the structure type pointed to by @rule_attr (only - * %LANDLOCK_RULE_PATH_BENEATH for now). + * @rule_type: Identify the structure type pointed to by @rule_attr: + * %LANDLOCK_RULE_PATH_BENEATH or %LANDLOCK_RULE_NET_PORT. * @rule_attr: Pointer to a rule (only of type &struct * landlock_path_beneath_attr for now). * @flags: Must be 0. @@ -291,9 +376,13 @@ out_fdput: * Possible returned errors are: * * - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time; + * - %EAFNOSUPPORT: @rule_type is %LANDLOCK_RULE_NET_PORT but TCP/IP is not + * supported by the running kernel; * - %EINVAL: @flags is not 0, or inconsistent access in the rule (i.e. - * &landlock_path_beneath_attr.allowed_access is not a subset of the - * ruleset handled accesses); + * &landlock_path_beneath_attr.allowed_access or + * &landlock_net_port_attr.allowed_access is not a subset of the + * ruleset handled accesses), or &landlock_net_port_attr.port is + * greater than 65535; * - %ENOMSG: Empty accesses (e.g. &landlock_path_beneath_attr.allowed_access); * - %EBADF: @ruleset_fd is not a file descriptor for the current thread, or a * member of @rule_attr is not a file descriptor as expected; @@ -306,10 +395,8 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd, const enum landlock_rule_type, rule_type, const void __user *const, rule_attr, const __u32, flags) { - struct landlock_path_beneath_attr path_beneath_attr; - struct path path; struct landlock_ruleset *ruleset; - int res, err; + int err; if (!landlock_initialized) return -EOPNOTSUPP; @@ -323,48 +410,17 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd, if (IS_ERR(ruleset)) return PTR_ERR(ruleset); - if (rule_type != LANDLOCK_RULE_PATH_BENEATH) { + switch (rule_type) { + case LANDLOCK_RULE_PATH_BENEATH: + err = add_rule_path_beneath(ruleset, rule_attr); + break; + case LANDLOCK_RULE_NET_PORT: + err = add_rule_net_port(ruleset, rule_attr); + break; + default: err = -EINVAL; - goto out_put_ruleset; - } - - /* Copies raw user space buffer, only one type for now. */ - res = copy_from_user(&path_beneath_attr, rule_attr, - sizeof(path_beneath_attr)); - if (res) { - err = -EFAULT; - goto out_put_ruleset; + break; } - - /* - * Informs about useless rule: empty allowed_access (i.e. deny rules) - * are ignored in path walks. - */ - if (!path_beneath_attr.allowed_access) { - err = -ENOMSG; - goto out_put_ruleset; - } - /* - * Checks that allowed_access matches the @ruleset constraints - * (ruleset->fs_access_masks[0] is automatically upgraded to 64-bits). - */ - if ((path_beneath_attr.allowed_access | ruleset->fs_access_masks[0]) != - ruleset->fs_access_masks[0]) { - err = -EINVAL; - goto out_put_ruleset; - } - - /* Gets and checks the new rule. */ - err = get_path_from_fd(path_beneath_attr.parent_fd, &path); - if (err) - goto out_put_ruleset; - - /* Imports the new rule. */ - err = landlock_append_fs_rule(ruleset, &path, - path_beneath_attr.allowed_access); - path_put(&path); - -out_put_ruleset: landlock_put_ruleset(ruleset); return err; } diff --git a/security/security.c b/security/security.c index 840a3d58a2..2cfecdb054 100644 --- a/security/security.c +++ b/security/security.c @@ -957,7 +957,7 @@ int security_capable(const struct cred *cred, * * Return: Returns 0 if permission is granted. */ -int security_quotactl(int cmds, int type, int id, struct super_block *sb) +int security_quotactl(int cmds, int type, int id, const struct super_block *sb) { return call_int_hook(quotactl, 0, cmds, type, id, sb); } @@ -1079,7 +1079,7 @@ int security_bprm_creds_for_exec(struct linux_binprm *bprm) * * Return: Returns 0 if the hook is successful and permission is granted. */ -int security_bprm_creds_from_file(struct linux_binprm *bprm, struct file *file) +int security_bprm_creds_from_file(struct linux_binprm *bprm, const struct file *file) { return call_int_hook(bprm_creds_from_file, 0, bprm, file); } @@ -1118,7 +1118,7 @@ int security_bprm_check(struct linux_binprm *bprm) * open file descriptors to which access will no longer be granted when the * attributes are changed. This is called immediately before commit_creds(). */ -void security_bprm_committing_creds(struct linux_binprm *bprm) +void security_bprm_committing_creds(const struct linux_binprm *bprm) { call_void_hook(bprm_committing_creds, bprm); } @@ -1134,7 +1134,7 @@ void security_bprm_committing_creds(struct linux_binprm *bprm) * process such as clearing out non-inheritable signal state. This is called * immediately after commit_creds(). */ -void security_bprm_committed_creds(struct linux_binprm *bprm) +void security_bprm_committed_creds(const struct linux_binprm *bprm) { call_void_hook(bprm_committed_creds, bprm); } @@ -1319,7 +1319,7 @@ EXPORT_SYMBOL(security_sb_remount); * * Return: Returns 0 if permission is granted. */ -int security_sb_kern_mount(struct super_block *sb) +int security_sb_kern_mount(const struct super_block *sb) { return call_int_hook(sb_kern_mount, 0, sb); } @@ -3975,7 +3975,7 @@ void security_inode_invalidate_secctx(struct inode *inode) EXPORT_SYMBOL(security_inode_invalidate_secctx); /** - * security_inode_notifysecctx() - Nofify the LSM of an inode's security label + * security_inode_notifysecctx() - Notify the LSM of an inode's security label * @inode: inode * @ctx: secctx * @ctxlen: length of secctx @@ -4030,7 +4030,19 @@ EXPORT_SYMBOL(security_inode_setsecctx); */ int security_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen) { - return call_int_hook(inode_getsecctx, -EOPNOTSUPP, inode, ctx, ctxlen); + struct security_hook_list *hp; + int rc; + + /* + * Only one module will provide a security context. + */ + hlist_for_each_entry(hp, &security_hook_heads.inode_getsecctx, list) { + rc = hp->hook.inode_getsecctx(inode, ctx, ctxlen); + if (rc != LSM_RET_DEFAULT(inode_getsecctx)) + return rc; + } + + return LSM_RET_DEFAULT(inode_getsecctx); } EXPORT_SYMBOL(security_inode_getsecctx); @@ -4387,8 +4399,20 @@ EXPORT_SYMBOL(security_sock_rcv_skb); int security_socket_getpeersec_stream(struct socket *sock, sockptr_t optval, sockptr_t optlen, unsigned int len) { - return call_int_hook(socket_getpeersec_stream, -ENOPROTOOPT, sock, - optval, optlen, len); + struct security_hook_list *hp; + int rc; + + /* + * Only one module will provide a security context. + */ + hlist_for_each_entry(hp, &security_hook_heads.socket_getpeersec_stream, + list) { + rc = hp->hook.socket_getpeersec_stream(sock, optval, optlen, + len); + if (rc != LSM_RET_DEFAULT(socket_getpeersec_stream)) + return rc; + } + return LSM_RET_DEFAULT(socket_getpeersec_stream); } /** @@ -4408,8 +4432,19 @@ int security_socket_getpeersec_stream(struct socket *sock, sockptr_t optval, int security_socket_getpeersec_dgram(struct socket *sock, struct sk_buff *skb, u32 *secid) { - return call_int_hook(socket_getpeersec_dgram, -ENOPROTOOPT, sock, - skb, secid); + struct security_hook_list *hp; + int rc; + + /* + * Only one module will provide a security context. + */ + hlist_for_each_entry(hp, &security_hook_heads.socket_getpeersec_dgram, + list) { + rc = hp->hook.socket_getpeersec_dgram(sock, skb, secid); + if (rc != LSM_RET_DEFAULT(socket_getpeersec_dgram)) + return rc; + } + return LSM_RET_DEFAULT(socket_getpeersec_dgram); } EXPORT_SYMBOL(security_socket_getpeersec_dgram); diff --git a/security/selinux/Kconfig b/security/selinux/Kconfig index d30348fbe0..61abc1e094 100644 --- a/security/selinux/Kconfig +++ b/security/selinux/Kconfig @@ -77,3 +77,13 @@ config SECURITY_SELINUX_DEBUG This enables debugging code designed to help SELinux kernel developers, unless you know what this does in the kernel code you should leave this disabled. + + To fine control the messages to be printed enable + CONFIG_DYNAMIC_DEBUG and see + Documentation/admin-guide/dynamic-debug-howto.rst for additional + information. + + Example usage: + + echo -n 'file "security/selinux/*" +p' > \ + /proc/dynamic_debug/control diff --git a/security/selinux/Makefile b/security/selinux/Makefile index 8363796390..c47519ed81 100644 --- a/security/selinux/Makefile +++ b/security/selinux/Makefile @@ -12,6 +12,8 @@ obj-$(CONFIG_SECURITY_SELINUX) := selinux.o ccflags-y := -I$(srctree)/security/selinux -I$(srctree)/security/selinux/include +ccflags-$(CONFIG_SECURITY_SELINUX_DEBUG) += -DDEBUG + selinux-y := avc.o hooks.o selinuxfs.o netlink.o nlmsgtab.o netif.o \ netnode.o netport.o status.o \ ss/ebitmap.o ss/hashtab.o ss/symtab.o ss/sidtab.o ss/avtab.o \ diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 53cfeefb2f..102a1f0c09 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -1935,7 +1935,7 @@ static inline int may_rename(struct inode *old_dir, /* Check whether a task can perform a filesystem operation. */ static int superblock_has_perm(const struct cred *cred, - struct super_block *sb, + const struct super_block *sb, u32 perms, struct common_audit_data *ad) { @@ -2137,7 +2137,7 @@ static int selinux_capable(const struct cred *cred, struct user_namespace *ns, return cred_has_capability(cred, cap, opts, ns == &init_user_ns); } -static int selinux_quotactl(int cmds, int type, int id, struct super_block *sb) +static int selinux_quotactl(int cmds, int type, int id, const struct super_block *sb) { const struct cred *cred = current_cred(); int rc = 0; @@ -2453,7 +2453,7 @@ static inline void flush_unauthorized_files(const struct cred *cred, /* * Prepare a process for imminent new credential changes due to exec */ -static void selinux_bprm_committing_creds(struct linux_binprm *bprm) +static void selinux_bprm_committing_creds(const struct linux_binprm *bprm) { struct task_security_struct *new_tsec; struct rlimit *rlim, *initrlim; @@ -2499,7 +2499,7 @@ static void selinux_bprm_committing_creds(struct linux_binprm *bprm) * Clean up the process immediately after the installation of new credentials * due to exec */ -static void selinux_bprm_committed_creds(struct linux_binprm *bprm) +static void selinux_bprm_committed_creds(const struct linux_binprm *bprm) { const struct task_security_struct *tsec = selinux_cred(current_cred()); u32 osid, sid; @@ -2719,7 +2719,7 @@ out_bad_option: return -EINVAL; } -static int selinux_sb_kern_mount(struct super_block *sb) +static int selinux_sb_kern_mount(const struct super_block *sb) { const struct cred *cred = current_cred(); struct common_audit_data ad; diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index 6fa6402632..6c596ae7fe 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -1198,7 +1198,7 @@ static struct inode *sel_make_inode(struct super_block *sb, umode_t mode) if (ret) { ret->i_mode = mode; - ret->i_atime = ret->i_mtime = inode_set_ctime_current(ret); + simple_inode_init_ts(ret); } return ret; } diff --git a/security/selinux/ss/avtab.c b/security/selinux/ss/avtab.c index 86d98a8e29..8751a602ea 100644 --- a/security/selinux/ss/avtab.c +++ b/security/selinux/ss/avtab.c @@ -17,6 +17,7 @@ * Tuned number of hash slots for avtab to reduce memory usage */ +#include <linux/bitops.h> #include <linux/kernel.h> #include <linux/slab.h> #include <linux/errno.h> @@ -66,8 +67,7 @@ static inline u32 avtab_hash(const struct avtab_key *keyp, u32 mask) } static struct avtab_node* -avtab_insert_node(struct avtab *h, u32 hvalue, - struct avtab_node *prev, +avtab_insert_node(struct avtab *h, struct avtab_node **dst, const struct avtab_key *key, const struct avtab_datum *datum) { struct avtab_node *newnode; @@ -89,15 +89,8 @@ avtab_insert_node(struct avtab *h, u32 hvalue, newnode->datum.u.data = datum->u.data; } - if (prev) { - newnode->next = prev->next; - prev->next = newnode; - } else { - struct avtab_node **n = &h->htable[hvalue]; - - newnode->next = *n; - *n = newnode; - } + newnode->next = *dst; + *dst = newnode; h->nel++; return newnode; @@ -137,7 +130,8 @@ static int avtab_insert(struct avtab *h, const struct avtab_key *key, break; } - newnode = avtab_insert_node(h, hvalue, prev, key, datum); + newnode = avtab_insert_node(h, prev ? &prev->next : &h->htable[hvalue], + key, datum); if (!newnode) return -ENOMEM; @@ -177,7 +171,8 @@ struct avtab_node *avtab_insert_nonunique(struct avtab *h, key->target_class < cur->key.target_class) break; } - return avtab_insert_node(h, hvalue, prev, key, datum); + return avtab_insert_node(h, prev ? &prev->next : &h->htable[hvalue], + key, datum); } /* This search function returns a node pointer, and can be used in @@ -298,13 +293,7 @@ int avtab_alloc(struct avtab *h, u32 nrules) u32 nslot = 0; if (nrules != 0) { - u32 shift = 1; - u32 work = nrules >> 3; - while (work) { - work >>= 1; - shift++; - } - nslot = 1 << shift; + nslot = nrules > 3 ? rounddown_pow_of_two(nrules / 2) : 2; if (nslot > MAX_AVTAB_HASH_BUCKETS) nslot = MAX_AVTAB_HASH_BUCKETS; @@ -349,7 +338,7 @@ void avtab_hash_eval(struct avtab *h, const char *tag) } pr_debug("SELinux: %s: %d entries and %d/%d buckets used, " - "longest chain length %d sum of chain length^2 %llu\n", + "longest chain length %d, sum of chain length^2 %llu\n", tag, h->nel, slots_used, h->nslot, max_chain_len, chain2_len_sum); } @@ -477,11 +466,7 @@ int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol, return -EINVAL; } - set = 0; - for (i = 0; i < ARRAY_SIZE(spec_order); i++) { - if (key.specified & spec_order[i]) - set++; - } + set = hweight16(key.specified & (AVTAB_XPERMS | AVTAB_TYPE | AVTAB_AV)); if (!set || set > 1) { pr_err("SELinux: avtab: more than one specifier\n"); return -EINVAL; diff --git a/security/selinux/ss/hashtab.c b/security/selinux/ss/hashtab.c index ac5cdddfbf..c05d8346a9 100644 --- a/security/selinux/ss/hashtab.c +++ b/security/selinux/ss/hashtab.c @@ -107,10 +107,12 @@ int hashtab_map(struct hashtab *h, void hashtab_stat(struct hashtab *h, struct hashtab_info *info) { u32 i, chain_len, slots_used, max_chain_len; + u64 chain2_len_sum; struct hashtab_node *cur; slots_used = 0; max_chain_len = 0; + chain2_len_sum = 0; for (i = 0; i < h->size; i++) { cur = h->htable[i]; if (cur) { @@ -123,11 +125,14 @@ void hashtab_stat(struct hashtab *h, struct hashtab_info *info) if (chain_len > max_chain_len) max_chain_len = chain_len; + + chain2_len_sum += (u64)chain_len * chain_len; } } info->slots_used = slots_used; info->max_chain_len = max_chain_len; + info->chain2_len_sum = chain2_len_sum; } #endif /* CONFIG_SECURITY_SELINUX_DEBUG */ diff --git a/security/selinux/ss/hashtab.h b/security/selinux/ss/hashtab.h index f9713b56d3..09b0a37449 100644 --- a/security/selinux/ss/hashtab.h +++ b/security/selinux/ss/hashtab.h @@ -38,6 +38,7 @@ struct hashtab { struct hashtab_info { u32 slots_used; u32 max_chain_len; + u64 chain2_len_sum; }; /* diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index 2d528f699a..595a435ea9 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -491,7 +491,7 @@ static u32 role_trans_hash(const void *k) { const struct role_trans_key *key = k; - return key->role + (key->type << 3) + (key->tclass << 5); + return jhash_3words(key->role, key->type, (u32)key->tclass << 16 | key->tclass, 0); } static int role_trans_cmp(const void *k1, const void *k2) @@ -684,9 +684,9 @@ static void hash_eval(struct hashtab *h, const char *hash_name) struct hashtab_info info; hashtab_stat(h, &info); - pr_debug("SELinux: %s: %d entries and %d/%d buckets used, longest chain length %d\n", + pr_debug("SELinux: %s: %d entries and %d/%d buckets used, longest chain length %d, sum of chain length^2 %llu\n", hash_name, h->nel, info.slots_used, h->size, - info.max_chain_len); + info.max_chain_len, info.chain2_len_sum); } static void symtab_hash_eval(struct symtab *s) diff --git a/security/selinux/ss/sidtab.c b/security/selinux/ss/sidtab.c index d8ead463b8..732fd8e22a 100644 --- a/security/selinux/ss/sidtab.c +++ b/security/selinux/ss/sidtab.c @@ -25,7 +25,7 @@ struct sidtab_str_cache { struct list_head lru_member; struct sidtab_entry *parent; u32 len; - char str[]; + char str[] __counted_by(len); }; #define index_to_sid(index) ((index) + SECINITSID_NUM + 1) diff --git a/security/tomoyo/tomoyo.c b/security/tomoyo/tomoyo.c index 298d182759..1bf4b3da9c 100644 --- a/security/tomoyo/tomoyo.c +++ b/security/tomoyo/tomoyo.c @@ -52,7 +52,7 @@ static int tomoyo_cred_prepare(struct cred *new, const struct cred *old, * * @bprm: Pointer to "struct linux_binprm". */ -static void tomoyo_bprm_committed_creds(struct linux_binprm *bprm) +static void tomoyo_bprm_committed_creds(const struct linux_binprm *bprm) { /* Clear old_domain_info saved by execve() request. */ struct tomoyo_task *s = tomoyo_task(current); |