diff options
Diffstat (limited to 'security')
64 files changed, 2084 insertions, 1122 deletions
diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig index f334e7cccf2d..e0d1dd0a192a 100644 --- a/security/apparmor/Kconfig +++ b/security/apparmor/Kconfig @@ -85,8 +85,8 @@ config SECURITY_APPARMOR_HASH_DEFAULT config SECURITY_APPARMOR_EXPORT_BINARY bool "Allow exporting the raw binary policy" depends on SECURITY_APPARMOR_INTROSPECT_POLICY - select ZLIB_INFLATE - select ZLIB_DEFLATE + select ZSTD_COMPRESS + select ZSTD_DECOMPRESS default y help This option allows reading back binary policy as it was loaded. diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index 065f4e346553..b9c5879dd599 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -5,7 +5,8 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o task.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ - resource.o secid.o file.o policy_ns.o label.o mount.o net.o + resource.o secid.o file.o policy_ns.o label.o mount.o net.o \ + policy_compat.o apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o obj-$(CONFIG_SECURITY_APPARMOR_KUNIT_TEST) += apparmor_policy_unpack_test.o diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index d066ccc219e2..424b2c1e586d 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -21,7 +21,7 @@ #include <linux/fs.h> #include <linux/fs_context.h> #include <linux/poll.h> -#include <linux/zlib.h> +#include <linux/zstd.h> #include <uapi/linux/major.h> #include <uapi/linux/magic.h> @@ -611,29 +611,30 @@ static const struct file_operations aa_fs_ns_revision_fops = { static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms, const char *match_str, size_t match_len) { + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); struct aa_perms tmp = { }; - struct aa_dfa *dfa; - unsigned int state = 0; + aa_state_t state = DFA_NOMATCH; if (profile_unconfined(profile)) return; - if (profile->file.dfa && *match_str == AA_CLASS_FILE) { - dfa = profile->file.dfa; - state = aa_dfa_match_len(dfa, profile->file.start, + 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_compute_fperms(dfa, state, &cond); + tmp = *(aa_lookup_fperms(&(rules->file), state, &cond)); } - } else if (profile->policy.dfa) { - if (!PROFILE_MEDIATES(profile, *match_str)) + } else if (rules->policy.dfa) { + if (!RULE_MEDIATES(rules, *match_str)) return; /* no change to current perms */ - dfa = profile->policy.dfa; - state = aa_dfa_match_len(dfa, profile->policy.start[0], + state = aa_dfa_match_len(rules->policy.dfa, + rules->policy.start[0], match_str, match_len); if (state) - aa_compute_perms(dfa, state, &tmp); + tmp = *aa_lookup_perms(&rules->policy, state); } aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum_raw(perms, &tmp); @@ -868,8 +869,10 @@ static struct multi_transaction *multi_transaction_new(struct file *file, if (!t) return ERR_PTR(-ENOMEM); kref_init(&t->count); - if (copy_from_user(t->data, buf, size)) + if (copy_from_user(t->data, buf, size)) { + put_multi_transaction(t); return ERR_PTR(-EFAULT); + } return t; } @@ -1090,9 +1093,9 @@ static int seq_profile_attach_show(struct seq_file *seq, void *v) struct aa_proxy *proxy = seq->private; struct aa_label *label = aa_get_label_rcu(&proxy->label); struct aa_profile *profile = labels_profile(label); - if (profile->attach) - seq_printf(seq, "%s\n", profile->attach); - else if (profile->xmatch) + if (profile->attach.xmatch_str) + seq_printf(seq, "%s\n", profile->attach.xmatch_str); + else if (profile->attach.xmatch.dfa) seq_puts(seq, "<unknown>\n"); else seq_printf(seq, "%s\n", profile->base.name); @@ -1197,10 +1200,24 @@ static int seq_ns_name_show(struct seq_file *seq, void *v) return 0; } +static int seq_ns_compress_min_show(struct seq_file *seq, void *v) +{ + seq_printf(seq, "%d\n", AA_MIN_CLEVEL); + return 0; +} + +static int seq_ns_compress_max_show(struct seq_file *seq, void *v) +{ + seq_printf(seq, "%d\n", AA_MAX_CLEVEL); + return 0; +} + SEQ_NS_FOPS(stacked); SEQ_NS_FOPS(nsstacked); SEQ_NS_FOPS(level); SEQ_NS_FOPS(name); +SEQ_NS_FOPS(compress_min); +SEQ_NS_FOPS(compress_max); /* policy/raw_data/ * file ops */ @@ -1295,42 +1312,34 @@ SEQ_RAWDATA_FOPS(revision); SEQ_RAWDATA_FOPS(hash); SEQ_RAWDATA_FOPS(compressed_size); -static int deflate_decompress(char *src, size_t slen, char *dst, size_t dlen) +static int decompress_zstd(char *src, size_t slen, char *dst, size_t dlen) { #ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY - if (aa_g_rawdata_compression_level != 0) { - int error = 0; - struct z_stream_s strm; - - memset(&strm, 0, sizeof(strm)); - - strm.workspace = kvzalloc(zlib_inflate_workspacesize(), GFP_KERNEL); - if (!strm.workspace) - return -ENOMEM; - - strm.next_in = src; - strm.avail_in = slen; - - error = zlib_inflateInit(&strm); - if (error != Z_OK) { - error = -ENOMEM; - goto fail_inflate_init; + if (slen < dlen) { + const size_t wksp_len = zstd_dctx_workspace_bound(); + zstd_dctx *ctx; + void *wksp; + size_t out_len; + int ret = 0; + + wksp = kvzalloc(wksp_len, GFP_KERNEL); + if (!wksp) { + ret = -ENOMEM; + goto cleanup; } - - strm.next_out = dst; - strm.avail_out = dlen; - - error = zlib_inflate(&strm, Z_FINISH); - if (error != Z_STREAM_END) - error = -EINVAL; - else - error = 0; - - zlib_inflateEnd(&strm); -fail_inflate_init: - kvfree(strm.workspace); - - return error; + ctx = zstd_init_dctx(wksp, wksp_len); + if (ctx == NULL) { + ret = -ENOMEM; + goto cleanup; + } + out_len = zstd_decompress_dctx(ctx, dst, dlen, src, slen); + if (zstd_is_error(out_len)) { + ret = -EINVAL; + goto cleanup; + } +cleanup: + kvfree(wksp); + return ret; } #endif @@ -1379,9 +1388,9 @@ static int rawdata_open(struct inode *inode, struct file *file) private->loaddata = loaddata; - error = deflate_decompress(loaddata->data, loaddata->compressed_size, - RAWDATA_F_DATA_BUF(private), - loaddata->size); + error = decompress_zstd(loaddata->data, loaddata->compressed_size, + RAWDATA_F_DATA_BUF(private), + loaddata->size); if (error) goto fail_decompress; @@ -2392,6 +2401,8 @@ static struct aa_sfs_entry aa_sfs_entry_apparmor[] = { AA_SFS_FILE_FOPS(".ns_level", 0444, &seq_ns_level_fops), AA_SFS_FILE_FOPS(".ns_name", 0444, &seq_ns_name_fops), AA_SFS_FILE_FOPS("profiles", 0444, &aa_sfs_profiles_fops), + AA_SFS_FILE_FOPS("raw_data_compression_level_min", 0444, &seq_ns_compress_min_fops), + AA_SFS_FILE_FOPS("raw_data_compression_level_max", 0444, &seq_ns_compress_max_fops), AA_SFS_DIR("features", aa_sfs_entry_features), { } }; diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c index 704b0c895605..5a7978aa4b19 100644 --- a/security/apparmor/audit.c +++ b/security/apparmor/audit.c @@ -36,6 +36,43 @@ static const char *const aa_audit_type[] = { "AUTO" }; +static const char *const aa_class_names[] = { + "none", + "unknown", + "file", + "cap", + "net", + "rlimits", + "domain", + "mount", + "unknown", + "ptrace", + "signal", + "xmatch", + "unknown", + "unknown", + "net", + "unknown", + "label", + "posix_mqueue", + "io_uring", + "module", + "lsm", + "unknown", + "unknown", + "unknown", + "unknown", + "unknown", + "unknown", + "unknown", + "unknown", + "unknown", + "unknown", + "X", + "dbus", +}; + + /* * Currently AppArmor auditing is fed straight into the audit framework. * @@ -46,7 +83,7 @@ static const char *const aa_audit_type[] = { */ /** - * audit_base - core AppArmor function. + * audit_pre() - core AppArmor function. * @ab: audit buffer to fill (NOT NULL) * @ca: audit structure containing data to audit (NOT NULL) * @@ -65,6 +102,12 @@ static void audit_pre(struct audit_buffer *ab, void *ca) audit_log_format(ab, " operation=\"%s\"", aad(sa)->op); } + if (aad(sa)->class) + audit_log_format(ab, " class=\"%s\"", + aad(sa)->class <= AA_CLASS_LAST ? + aa_class_names[aad(sa)->class] : + "unknown"); + if (aad(sa)->info) { audit_log_format(ab, " info=\"%s\"", aad(sa)->info); if (aad(sa)->error) diff --git a/security/apparmor/capability.c b/security/apparmor/capability.c index deccea8654ad..326a51838ef2 100644 --- a/security/apparmor/capability.c +++ b/security/apparmor/capability.c @@ -64,6 +64,8 @@ static void audit_cb(struct audit_buffer *ab, void *va) static int audit_caps(struct common_audit_data *sa, struct aa_profile *profile, int cap, int error) { + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); struct audit_cache *ent; int type = AUDIT_APPARMOR_AUTO; @@ -72,13 +74,13 @@ static int audit_caps(struct common_audit_data *sa, struct aa_profile *profile, if (likely(!error)) { /* test if auditing is being forced */ if (likely((AUDIT_MODE(profile) != AUDIT_ALL) && - !cap_raised(profile->caps.audit, cap))) + !cap_raised(rules->caps.audit, cap))) return 0; type = AUDIT_APPARMOR_AUDIT; } else if (KILL_MODE(profile) || - cap_raised(profile->caps.kill, cap)) { + cap_raised(rules->caps.kill, cap)) { type = AUDIT_APPARMOR_KILL; - } else if (cap_raised(profile->caps.quiet, cap) && + } else if (cap_raised(rules->caps.quiet, cap) && AUDIT_MODE(profile) != AUDIT_NOQUIET && AUDIT_MODE(profile) != AUDIT_ALL) { /* quiet auditing */ @@ -114,10 +116,12 @@ static int audit_caps(struct common_audit_data *sa, struct aa_profile *profile, static int profile_capable(struct aa_profile *profile, int cap, unsigned int opts, struct common_audit_data *sa) { + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); int error; - if (cap_raised(profile->caps.allow, cap) && - !cap_raised(profile->caps.denied, cap)) + if (cap_raised(rules->caps.allow, cap) && + !cap_raised(rules->caps.denied, cap)) error = 0; else error = -EPERM; @@ -148,7 +152,7 @@ int aa_capable(struct aa_label *label, int cap, unsigned int opts) { struct aa_profile *profile; int error = 0; - DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_CAP, OP_CAPABLE); + DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_CAP, AA_CLASS_CAP, OP_CAPABLE); sa.u.cap = cap; error = fn_for_each_confined(label, profile, diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 7bafb4c4767c..6dd3cc5309bf 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -30,24 +30,6 @@ #include "include/policy_ns.h" /** - * aa_free_domain_entries - free entries in a domain table - * @domain: the domain table to free (MAYBE NULL) - */ -void aa_free_domain_entries(struct aa_domain *domain) -{ - int i; - if (domain) { - if (!domain->table) - return; - - for (i = 0; i < domain->size; i++) - kfree_sensitive(domain->table[i]); - kfree_sensitive(domain->table); - domain->table = NULL; - } -} - -/** * may_change_ptraced_domain - check if can change profile on ptraced task * @to_label: profile to change to (NOT NULL) * @info: message if there is an error @@ -95,23 +77,25 @@ out: * If a subns profile is not to be matched should be prescreened with * visibility test. */ -static inline unsigned int match_component(struct aa_profile *profile, - struct aa_profile *tp, - bool stack, unsigned int state) +static inline aa_state_t match_component(struct aa_profile *profile, + struct aa_profile *tp, + bool stack, aa_state_t state) { + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); const char *ns_name; if (stack) - state = aa_dfa_match(profile->file.dfa, state, "&"); + state = aa_dfa_match(rules->file.dfa, state, "&"); if (profile->ns == tp->ns) - return aa_dfa_match(profile->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(profile->file.dfa, state, ":", 1); - state = aa_dfa_match(profile->file.dfa, state, ns_name); - state = aa_dfa_match_len(profile->file.dfa, state, ":", 1); - return aa_dfa_match(profile->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); } /** @@ -132,9 +116,11 @@ static inline unsigned int match_component(struct aa_profile *profile, */ static int label_compound_match(struct aa_profile *profile, struct aa_label *label, bool stack, - unsigned int state, bool subns, u32 request, + aa_state_t state, bool subns, u32 request, struct aa_perms *perms) { + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); struct aa_profile *tp; struct label_it i; struct path_cond cond = { }; @@ -157,12 +143,12 @@ next: label_for_each_cont(i, label, tp) { if (!aa_ns_visible(profile->ns, tp->ns, subns)) continue; - state = aa_dfa_match(profile->file.dfa, state, "//&"); + state = aa_dfa_match(rules->file.dfa, state, "//&"); state = match_component(profile, tp, false, state); if (!state) goto fail; } - *perms = aa_compute_fperms(profile->file.dfa, state, &cond); + *perms = *(aa_lookup_fperms(&(rules->file), state, &cond)); aa_apply_modes_to_perms(profile, perms); if ((perms->allow & request) != request) return -EACCES; @@ -192,14 +178,16 @@ fail: */ static int label_components_match(struct aa_profile *profile, struct aa_label *label, bool stack, - unsigned int start, bool subns, u32 request, + aa_state_t start, bool subns, u32 request, struct aa_perms *perms) { + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); struct aa_profile *tp; struct label_it i; struct aa_perms tmp; struct path_cond cond = { }; - unsigned int state = 0; + aa_state_t state = 0; /* find first subcomponent to test */ label_for_each(i, label, tp) { @@ -215,7 +203,7 @@ static int label_components_match(struct aa_profile *profile, return 0; next: - tmp = aa_compute_fperms(profile->file.dfa, 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) { @@ -224,7 +212,7 @@ next: state = match_component(profile, tp, stack, start); if (!state) goto fail; - tmp = aa_compute_fperms(profile->file.dfa, state, &cond); + tmp = *(aa_lookup_fperms(&(rules->file), state, &cond)); aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum(perms, &tmp); } @@ -252,7 +240,7 @@ fail: * Returns: the state the match finished in, may be the none matching state */ static int label_match(struct aa_profile *profile, struct aa_label *label, - bool stack, unsigned int state, bool subns, u32 request, + bool stack, aa_state_t state, bool subns, u32 request, struct aa_perms *perms) { int error; @@ -286,7 +274,7 @@ static int label_match(struct aa_profile *profile, struct aa_label *label, */ static int change_profile_perms(struct aa_profile *profile, struct aa_label *target, bool stack, - u32 request, unsigned int start, + u32 request, aa_state_t start, struct aa_perms *perms) { if (profile_unconfined(profile)) { @@ -308,45 +296,47 @@ static int change_profile_perms(struct aa_profile *profile, * Returns: number of extended attributes that matched, or < 0 on error */ static int aa_xattrs_match(const struct linux_binprm *bprm, - struct aa_profile *profile, unsigned int state) + struct aa_profile *profile, aa_state_t state) { int i; - ssize_t size; struct dentry *d; char *value = NULL; - int value_size = 0, ret = profile->xattr_count; + struct aa_attachment *attach = &profile->attach; + int size, value_size = 0, ret = attach->xattr_count; - if (!bprm || !profile->xattr_count) + if (!bprm || !attach->xattr_count) return 0; might_sleep(); /* transition from exec match to xattr set */ - state = aa_dfa_outofband_transition(profile->xmatch, state); + state = aa_dfa_outofband_transition(attach->xmatch.dfa, state); d = bprm->file->f_path.dentry; - for (i = 0; i < profile->xattr_count; i++) { - size = vfs_getxattr_alloc(&init_user_ns, d, profile->xattrs[i], + for (i = 0; i < attach->xattr_count; i++) { + size = vfs_getxattr_alloc(&init_user_ns, d, attach->xattrs[i], &value, value_size, GFP_KERNEL); if (size >= 0) { - u32 perm; + u32 index, perm; /* * Check the xattr presence before value. This ensure * that not present xattr can be distinguished from a 0 * length value or rule that matches any value */ - state = aa_dfa_null_transition(profile->xmatch, state); + state = aa_dfa_null_transition(attach->xmatch.dfa, + state); /* Check xattr value */ - state = aa_dfa_match_len(profile->xmatch, state, value, - size); - perm = dfa_user_allow(profile->xmatch, 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; if (!(perm & MAY_EXEC)) { ret = -EINVAL; goto out; } } /* transition to next element */ - state = aa_dfa_outofband_transition(profile->xmatch, state); + state = aa_dfa_outofband_transition(attach->xmatch.dfa, state); if (size < 0) { /* * No xattr match, so verify if transition to @@ -398,6 +388,8 @@ static struct aa_label *find_attach(const struct linux_binprm *bprm, rcu_read_lock(); restart: list_for_each_entry_rcu(profile, head, base.list) { + struct aa_attachment *attach = &profile->attach; + if (profile->label.flags & FLAG_NULL && &profile->label == ns_unconfined(profile->ns)) continue; @@ -413,13 +405,16 @@ restart: * as another profile, signal a conflict and refuse to * match. */ - if (profile->xmatch) { - unsigned int state, count; - u32 perm; - - state = aa_dfa_leftmatch(profile->xmatch, DFA_START, - name, &count); - perm = dfa_user_allow(profile->xmatch, state); + 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], + name, &count); + 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; @@ -427,7 +422,7 @@ restart: if (count < candidate_len) continue; - if (bprm && profile->xattr_count) { + if (bprm && attach->xattr_count) { long rev = READ_ONCE(ns->revision); if (!aa_get_profile_not0(profile)) @@ -466,7 +461,7 @@ restart: * xattrs, or a longer match */ candidate = profile; - candidate_len = max(count, profile->xmatch_len); + candidate_len = max(count, attach->xmatch_len); candidate_xattrs = ret; conflict = false; } @@ -510,6 +505,8 @@ static const char *next_name(int xtype, const char *name) struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex, const char **name) { + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); struct aa_label *label = NULL; u32 xtype = xindex & AA_X_TYPE_MASK; int index = xindex & AA_X_INDEX_MASK; @@ -520,7 +517,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 = profile->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; @@ -559,6 +556,8 @@ static struct aa_label *x_to_label(struct aa_profile *profile, const char **lookupname, const char **info) { + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); struct aa_label *new = NULL; struct aa_ns *ns = profile->ns; u32 xtype = xindex & AA_X_TYPE_MASK; @@ -571,7 +570,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 = profile->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); @@ -625,9 +624,11 @@ static struct aa_label *profile_transition(struct aa_profile *profile, char *buffer, struct path_cond *cond, bool *secure_exec) { + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); struct aa_label *new = NULL; const char *info = NULL, *name = NULL, *target = NULL; - unsigned int state = profile->file.start; + aa_state_t state = rules->file.start[AA_CLASS_FILE]; struct aa_perms perms = {}; bool nonewprivs = false; int error = 0; @@ -661,7 +662,7 @@ static struct aa_label *profile_transition(struct aa_profile *profile, } /* find exec permissions for name */ - state = aa_str_perms(profile->file.dfa, 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, @@ -679,8 +680,8 @@ static struct aa_label *profile_transition(struct aa_profile *profile, /* no exec permission - learning mode */ struct aa_profile *new_profile = NULL; - new_profile = aa_new_null_profile(profile, false, name, - GFP_KERNEL); + new_profile = aa_new_learning_profile(profile, false, name, + GFP_KERNEL); if (!new_profile) { error = -ENOMEM; info = "could not create null profile"; @@ -723,7 +724,9 @@ static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec, char *buffer, struct path_cond *cond, bool *secure_exec) { - unsigned int state = profile->file.start; + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); + 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; @@ -756,7 +759,7 @@ static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec, } /* find exec permissions for name */ - state = aa_str_perms(profile->file.dfa, 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; @@ -765,7 +768,7 @@ static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec, * onexec permission is linked to exec with a standard pairing * exec\0change_profile */ - state = aa_dfa_null_transition(profile->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) { @@ -1005,8 +1008,8 @@ static struct aa_label *build_change_hat(struct aa_profile *profile, if (!hat) { error = -ENOENT; if (COMPLAIN_MODE(profile)) { - hat = aa_new_null_profile(profile, true, name, - GFP_KERNEL); + hat = aa_new_learning_profile(profile, true, name, + GFP_KERNEL); if (!hat) { info = "failed null profile create"; error = -ENOMEM; @@ -1262,12 +1265,15 @@ static int change_profile_perms_wrapper(const char *op, const char *name, struct aa_label *target, bool stack, u32 request, struct aa_perms *perms) { + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); const char *info = NULL; int error = 0; if (!error) error = change_profile_perms(profile, target, stack, request, - profile->file.start, perms); + rules->file.start[AA_CLASS_FILE], + perms); if (error) error = aa_audit_file(profile, perms, op, request, name, NULL, target, GLOBAL_ROOT_UID, info, @@ -1354,8 +1360,8 @@ int aa_change_profile(const char *fqname, int flags) !COMPLAIN_MODE(labels_profile(label))) goto audit; /* released below */ - tprofile = aa_new_null_profile(labels_profile(label), false, - fqname, GFP_KERNEL); + tprofile = aa_new_learning_profile(labels_profile(label), false, + fqname, GFP_KERNEL); if (!tprofile) { info = "failed null profile create"; error = -ENOMEM; diff --git a/security/apparmor/file.c b/security/apparmor/file.c index d43679894d23..cb3d3060d104 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -95,7 +95,7 @@ int aa_audit_file(struct aa_profile *profile, struct aa_perms *perms, kuid_t ouid, const char *info, int error) { int type = AUDIT_APPARMOR_AUTO; - DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_TASK, op); + DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_TASK, AA_CLASS_FILE, op); sa.u.tsk = NULL; aad(&sa)->request = request; @@ -141,19 +141,6 @@ int aa_audit_file(struct aa_profile *profile, struct aa_perms *perms, return aa_audit(type, profile, &sa, file_audit_cb); } -/** - * is_deleted - test if a file has been completely unlinked - * @dentry: dentry of file to test for deletion (NOT NULL) - * - * Returns: true if deleted else false - */ -static inline bool is_deleted(struct dentry *dentry) -{ - if (d_unlinked(dentry) && d_backing_inode(dentry)->i_nlink == 0) - return true; - return false; -} - static int path_name(const char *op, struct aa_label *label, const struct path *path, int flags, char *buffer, const char **name, struct path_cond *cond, u32 request) @@ -175,73 +162,28 @@ static int path_name(const char *op, struct aa_label *label, } /** - * map_old_perms - map old file perms layout to the new layout - * @old: permission set in old mapping - * - * Returns: new permission mapping - */ -static u32 map_old_perms(u32 old) -{ - u32 new = old & 0xf; - if (old & MAY_READ) - new |= AA_MAY_GETATTR | AA_MAY_OPEN; - if (old & MAY_WRITE) - new |= AA_MAY_SETATTR | AA_MAY_CREATE | AA_MAY_DELETE | - AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_OPEN; - if (old & 0x10) - new |= AA_MAY_LINK; - /* the old mapping lock and link_subset flags where overlaid - * and use was determined by part of a pair that they were in - */ - if (old & 0x20) - new |= AA_MAY_LOCK | AA_LINK_SUBSET; - if (old & 0x40) /* AA_EXEC_MMAP */ - new |= AA_EXEC_MMAP; - - return new; -} - -/** - * aa_compute_fperms - convert dfa compressed perms to internal perms - * @dfa: dfa to compute perms for (NOT NULL) + * aa_lookup_fperms - convert dfa compressed perms to internal perms + * @dfa: dfa to lookup perms for (NOT NULL) * @state: state in dfa * @cond: conditions to consider (NOT NULL) * - * TODO: convert from dfa + state to permission entry, do computation conversion - * at load time. + * TODO: convert from dfa + state to permission entry * - * Returns: computed permission set + * Returns: a pointer to a file permission set */ -struct aa_perms aa_compute_fperms(struct aa_dfa *dfa, unsigned int state, - struct path_cond *cond) +struct aa_perms default_perms = {}; +struct aa_perms *aa_lookup_fperms(struct aa_policydb *file_rules, + aa_state_t state, struct path_cond *cond) { - /* FIXME: change over to new dfa format - * currently file perms are encoded in the dfa, new format - * splits the permissions from the dfa. This mapping can be - * done at profile load - */ - struct aa_perms perms = { }; + unsigned int index = ACCEPT_TABLE(file_rules->dfa)[state]; - if (uid_eq(current_fsuid(), cond->uid)) { - perms.allow = map_old_perms(dfa_user_allow(dfa, state)); - perms.audit = map_old_perms(dfa_user_audit(dfa, state)); - perms.quiet = map_old_perms(dfa_user_quiet(dfa, state)); - perms.xindex = dfa_user_xindex(dfa, state); - } else { - perms.allow = map_old_perms(dfa_other_allow(dfa, state)); - perms.audit = map_old_perms(dfa_other_audit(dfa, state)); - perms.quiet = map_old_perms(dfa_other_quiet(dfa, state)); - perms.xindex = dfa_other_xindex(dfa, state); - } - perms.allow |= AA_MAY_GETATTR; + if (!(file_rules->perms)) + return &default_perms; - /* change_profile wasn't determined by ownership in old mapping */ - if (ACCEPT_TABLE(dfa)[state] & 0x80000000) - perms.allow |= AA_MAY_CHANGE_PROFILE; - if (ACCEPT_TABLE(dfa)[state] & 0x40000000) - perms.allow |= AA_MAY_ONEXEC; + if (uid_eq(current_fsuid(), cond->uid)) + return &(file_rules->perms[index]); - return perms; + return &(file_rules->perms[index + 1]); } /** @@ -254,26 +196,30 @@ struct aa_perms aa_compute_fperms(struct aa_dfa *dfa, unsigned int state, * * Returns: the final state in @dfa when beginning @start and walking @name */ -unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start, - const char *name, struct path_cond *cond, - struct aa_perms *perms) +aa_state_t aa_str_perms(struct aa_policydb *file_rules, aa_state_t start, + const char *name, struct path_cond *cond, + struct aa_perms *perms) { - unsigned int state; - state = aa_dfa_match(dfa, start, name); - *perms = aa_compute_fperms(dfa, state, cond); + aa_state_t state; + state = aa_dfa_match(file_rules->dfa, start, name); + *perms = *(aa_lookup_fperms(file_rules, state, cond)); return state; } -int __aa_path_perm(const char *op, struct aa_profile *profile, const char *name, - u32 request, struct path_cond *cond, int flags, - struct aa_perms *perms) +static int __aa_path_perm(const char *op, struct aa_profile *profile, + const char *name, u32 request, + struct path_cond *cond, int flags, + struct aa_perms *perms) { + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); int e = 0; if (profile_unconfined(profile)) return 0; - aa_str_perms(profile->file.dfa, profile->file.start, name, cond, perms); + aa_str_perms(&(rules->file), rules->file.start[AA_CLASS_FILE], + name, cond, perms); if (request & ~perms->allow) e = -EACCES; return aa_audit_file(profile, perms, op, request, name, NULL, NULL, @@ -360,11 +306,13 @@ static int profile_path_link(struct aa_profile *profile, const struct path *target, char *buffer2, struct path_cond *cond) { + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); const char *lname, *tname = NULL; struct aa_perms lperms = {}, perms; const char *info = NULL; u32 request = AA_MAY_LINK; - unsigned int state; + aa_state_t state; int error; error = path_name(OP_LINK, &profile->label, link, profile->path_flags, @@ -380,15 +328,16 @@ static int profile_path_link(struct aa_profile *profile, error = -EACCES; /* aa_str_perms - handles the case of the dfa being NULL */ - state = aa_str_perms(profile->file.dfa, profile->file.start, 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(profile->file.dfa, state); - aa_str_perms(profile->file.dfa, 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. @@ -410,8 +359,8 @@ static int profile_path_link(struct aa_profile *profile, /* Do link perm subset test requiring allowed permission on link are * a subset of the allowed permissions on target. */ - aa_str_perms(profile->file.dfa, profile->file.start, tname, cond, - &perms); + aa_str_perms(&(rules->file), rules->file.start[AA_CLASS_FILE], + tname, cond, &perms); /* AA_MAY_LINK is not considered in the subset test */ request = lperms.allow & ~AA_MAY_LINK; diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h index 9c3fc36a0702..8a81557c9d59 100644 --- a/security/apparmor/include/apparmor.h +++ b/security/apparmor/include/apparmor.h @@ -16,7 +16,7 @@ /* * Class of mediation types in the AppArmor policy db */ -#define AA_CLASS_ENTRY 0 +#define AA_CLASS_NONE 0 #define AA_CLASS_UNKNOWN 1 #define AA_CLASS_FILE 2 #define AA_CLASS_CAP 3 @@ -26,10 +26,18 @@ #define AA_CLASS_MOUNT 7 #define AA_CLASS_PTRACE 9 #define AA_CLASS_SIGNAL 10 +#define AA_CLASS_XMATCH 11 #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_LAST AA_CLASS_LABEL +#define AA_CLASS_X 31 +#define AA_CLASS_DBUS 32 + +#define AA_CLASS_LAST AA_CLASS_DBUS /* Control parameters settable through module/boot flags */ extern enum audit_mode aa_g_audit; @@ -43,4 +51,15 @@ extern bool aa_g_logsyscall; extern bool aa_g_paranoid_load; extern unsigned int aa_g_path_max; +#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY +#define AA_MIN_CLEVEL zstd_min_clevel() +#define AA_MAX_CLEVEL zstd_max_clevel() +#define AA_DEFAULT_CLEVEL ZSTD_CLEVEL_DEFAULT +#else +#define AA_MIN_CLEVEL 0 +#define AA_MAX_CLEVEL 0 +#define AA_DEFAULT_CLEVEL 0 +#endif /* CONFIG_SECURITY_APPARMOR_EXPORT_BINARY */ + + #endif /* __APPARMOR_H */ diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index 18519a4eb67e..c328f07f11cd 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -107,6 +107,7 @@ enum audit_type { struct apparmor_audit_data { int error; int type; + u16 class; const char *op; struct aa_label *label; const char *name; @@ -155,9 +156,12 @@ struct apparmor_audit_data { /* macros for dealing with apparmor_audit_data structure */ #define aad(SA) ((SA)->apparmor_audit_data) -#define DEFINE_AUDIT_DATA(NAME, T, X) \ +#define DEFINE_AUDIT_DATA(NAME, T, C, X) \ /* TODO: cleanup audit init so we don't need _aad = {0,} */ \ - struct apparmor_audit_data NAME ## _aad = { .op = (X), }; \ + struct apparmor_audit_data NAME ## _aad = { \ + .class = (C), \ + .op = (X), \ + }; \ struct common_audit_data NAME = \ { \ .type = (T), \ diff --git a/security/apparmor/include/cred.h b/security/apparmor/include/cred.h index 0b9ae4804ef7..58fdc72af664 100644 --- a/security/apparmor/include/cred.h +++ b/security/apparmor/include/cred.h @@ -64,19 +64,6 @@ static inline struct aa_label *aa_get_newest_cred_label(const struct cred *cred) } /** - * __aa_task_raw_label - retrieve another task's label - * @task: task to query (NOT NULL) - * - * Returns: @task's label without incrementing its ref count - * - * If @task != current needs to be called in RCU safe critical section - */ -static inline struct aa_label *__aa_task_raw_label(struct task_struct *task) -{ - return aa_cred_raw_label(__task_cred(task)); -} - -/** * aa_current_raw_label - find the current tasks confining label * * Returns: up to date confining label or the ns unconfined label (NOT NULL) diff --git a/security/apparmor/include/domain.h b/security/apparmor/include/domain.h index d14928fe1c6f..77f9a0ed0f04 100644 --- a/security/apparmor/include/domain.h +++ b/security/apparmor/include/domain.h @@ -16,11 +16,6 @@ #ifndef __AA_DOMAIN_H #define __AA_DOMAIN_H -struct aa_domain { - int size; - char **table; -}; - #define AA_CHANGE_NOFLAGS 0 #define AA_CHANGE_TEST 1 #define AA_CHANGE_CHILD 2 @@ -32,7 +27,6 @@ struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex, int apparmor_bprm_creds_for_exec(struct linux_binprm *bprm); -void aa_free_domain_entries(struct aa_domain *domain); int aa_change_hat(const char *hats[], int count, u64 token, int flags); int aa_change_profile(const char *fqname, int flags); diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index 029cb20e322d..5be620af33ba 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -17,6 +17,7 @@ #include "match.h" #include "perms.h" +struct aa_policydb; struct aa_profile; struct path; @@ -87,18 +88,17 @@ static inline struct aa_label *aa_get_file_label(struct aa_file_ctx *ctx) * - exec type - which determines how the executable name and index are used * - flags - which modify how the destination name is applied */ -#define AA_X_INDEX_MASK 0x03ff +#define AA_X_INDEX_MASK AA_INDEX_MASK -#define AA_X_TYPE_MASK 0x0c00 -#define AA_X_TYPE_SHIFT 10 -#define AA_X_NONE 0x0000 -#define AA_X_NAME 0x0400 /* use executable name px */ -#define AA_X_TABLE 0x0800 /* use a specified name ->n# */ +#define AA_X_TYPE_MASK 0x0c000000 +#define AA_X_NONE AA_INDEX_NONE +#define AA_X_NAME 0x04000000 /* use executable name px */ +#define AA_X_TABLE 0x08000000 /* use a specified name ->n# */ -#define AA_X_UNSAFE 0x1000 -#define AA_X_CHILD 0x2000 /* make >AA_X_NONE apply to children */ -#define AA_X_INHERIT 0x4000 -#define AA_X_UNCONFINED 0x8000 +#define AA_X_UNSAFE 0x10000000 +#define AA_X_CHILD 0x20000000 +#define AA_X_INHERIT 0x40000000 +#define AA_X_UNCONFINED 0x80000000 /* need to make conditional which ones are being set */ struct path_cond { @@ -108,90 +108,17 @@ struct path_cond { #define COMBINED_PERM_MASK(X) ((X).allow | (X).audit | (X).quiet | (X).kill) -/* FIXME: split perms from dfa and match this to description - * also add delegation info. - */ -static inline u16 dfa_map_xindex(u16 mask) -{ - u16 old_index = (mask >> 10) & 0xf; - u16 index = 0; - - if (mask & 0x100) - index |= AA_X_UNSAFE; - if (mask & 0x200) - index |= AA_X_INHERIT; - if (mask & 0x80) - index |= AA_X_UNCONFINED; - - if (old_index == 1) { - index |= AA_X_UNCONFINED; - } else if (old_index == 2) { - index |= AA_X_NAME; - } else if (old_index == 3) { - index |= AA_X_NAME | AA_X_CHILD; - } else if (old_index) { - index |= AA_X_TABLE; - index |= old_index - 4; - } - - return index; -} - -/* - * map old dfa inline permissions to new format - */ -#define dfa_user_allow(dfa, state) (((ACCEPT_TABLE(dfa)[state]) & 0x7f) | \ - ((ACCEPT_TABLE(dfa)[state]) & 0x80000000)) -#define dfa_user_xbits(dfa, state) (((ACCEPT_TABLE(dfa)[state]) >> 7) & 0x7f) -#define dfa_user_audit(dfa, state) ((ACCEPT_TABLE2(dfa)[state]) & 0x7f) -#define dfa_user_quiet(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 7) & 0x7f) -#define dfa_user_xindex(dfa, state) \ - (dfa_map_xindex(ACCEPT_TABLE(dfa)[state] & 0x3fff)) - -#define dfa_other_allow(dfa, state) ((((ACCEPT_TABLE(dfa)[state]) >> 14) & \ - 0x7f) | \ - ((ACCEPT_TABLE(dfa)[state]) & 0x80000000)) -#define dfa_other_xbits(dfa, state) \ - ((((ACCEPT_TABLE(dfa)[state]) >> 7) >> 14) & 0x7f) -#define dfa_other_audit(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 14) & 0x7f) -#define dfa_other_quiet(dfa, state) \ - ((((ACCEPT_TABLE2(dfa)[state]) >> 7) >> 14) & 0x7f) -#define dfa_other_xindex(dfa, state) \ - dfa_map_xindex((ACCEPT_TABLE(dfa)[state] >> 14) & 0x3fff) - int aa_audit_file(struct aa_profile *profile, struct aa_perms *perms, const char *op, u32 request, const char *name, const char *target, struct aa_label *tlabel, kuid_t ouid, const char *info, int error); -/** - * struct aa_file_rules - components used for file rule permissions - * @dfa: dfa to match path names and conditionals against - * @perms: permission table indexed by the matched state accept entry of @dfa - * @trans: transition table for indexed by named x transitions - * - * File permission are determined by matching a path against @dfa and - * then using the value of the accept entry for the matching state as - * an index into @perms. If a named exec transition is required it is - * looked up in the transition table. - */ -struct aa_file_rules { - unsigned int start; - struct aa_dfa *dfa; - /* struct perms perms; */ - struct aa_domain trans; - /* TODO: add delegate table */ -}; - -struct aa_perms aa_compute_fperms(struct aa_dfa *dfa, unsigned int state, - struct path_cond *cond); -unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start, - const char *name, struct path_cond *cond, - struct aa_perms *perms); +struct aa_perms *aa_lookup_fperms(struct aa_policydb *file_rules, + aa_state_t state, struct path_cond *cond); +aa_state_t aa_str_perms(struct aa_policydb *file_rules, aa_state_t start, + const char *name, struct path_cond *cond, + struct aa_perms *perms); -int __aa_path_perm(const char *op, struct aa_profile *profile, - const char *name, u32 request, struct path_cond *cond, - int flags, struct aa_perms *perms); int aa_path_perm(const char *op, struct aa_label *label, const struct path *path, int flags, u32 request, struct path_cond *cond); @@ -204,11 +131,6 @@ int aa_file_perm(const char *op, struct aa_label *label, struct file *file, void aa_inherit_files(const struct cred *cred, struct files_struct *files); -static inline void aa_free_file_rules(struct aa_file_rules *rules) -{ - aa_put_dfa(rules->dfa); - aa_free_domain_entries(&rules->trans); -} /** * aa_map_file_perms - map file flags to AppArmor permissions diff --git a/security/apparmor/include/label.h b/security/apparmor/include/label.h index 860484c6f99a..2a72e6b17d68 100644 --- a/security/apparmor/include/label.h +++ b/security/apparmor/include/label.h @@ -261,7 +261,7 @@ for ((I).i = (I).j = 0; \ struct label_it i; \ int ret = 0; \ label_for_each(i, (L), profile) { \ - if (PROFILE_MEDIATES(profile, (C))) { \ + if (RULE_MEDIATES(&profile->rules, (C))) { \ ret = 1; \ break; \ } \ @@ -333,7 +333,7 @@ struct aa_label *aa_label_parse(struct aa_label *base, const char *str, static inline const char *aa_label_strn_split(const char *str, int n) { const char *pos; - unsigned int state; + aa_state_t state; state = aa_dfa_matchn_until(stacksplitdfa, DFA_START, str, n, &pos); if (!ACCEPT_TABLE(stacksplitdfa)[state]) @@ -345,7 +345,7 @@ static inline const char *aa_label_strn_split(const char *str, int n) static inline const char *aa_label_str_split(const char *str) { const char *pos; - unsigned int state; + aa_state_t state; state = aa_dfa_match_until(stacksplitdfa, DFA_START, str, &pos); if (!ACCEPT_TABLE(stacksplitdfa)[state]) @@ -357,9 +357,10 @@ static inline const char *aa_label_str_split(const char *str) struct aa_perms; -int aa_label_match(struct aa_profile *profile, struct aa_label *label, - unsigned int state, bool subns, u32 request, - struct aa_perms *perms); +struct aa_ruleset; +int aa_label_match(struct aa_profile *profile, struct aa_ruleset *rules, + struct aa_label *label, aa_state_t state, bool subns, + u32 request, struct aa_perms *perms); /** diff --git a/security/apparmor/include/lib.h b/security/apparmor/include/lib.h index f42359f58eb5..f1a29ab7ea1b 100644 --- a/security/apparmor/include/lib.h +++ b/security/apparmor/include/lib.h @@ -87,8 +87,8 @@ static inline bool aa_strneq(const char *str, const char *sub, int len) * character which is not used in standard matching and is only * used to separate pairs. */ -static inline unsigned int aa_dfa_null_transition(struct aa_dfa *dfa, - unsigned int start) +static inline aa_state_t aa_dfa_null_transition(struct aa_dfa *dfa, + aa_state_t start) { /* the null transition only needs the string's null terminator byte */ return aa_dfa_next(dfa, start, 0); @@ -99,6 +99,12 @@ static inline bool path_mediated_fs(struct dentry *dentry) return !(dentry->d_sb->s_flags & SB_NOUSER); } +struct aa_str_table { + int size; + char **table; +}; + +void aa_free_str_table(struct aa_str_table *table); struct counted_str { struct kref count; diff --git a/security/apparmor/include/match.h b/security/apparmor/include/match.h index 884489590588..58fbf67139b9 100644 --- a/security/apparmor/include/match.h +++ b/security/apparmor/include/match.h @@ -125,19 +125,19 @@ static inline size_t table_size(size_t len, size_t el_size) 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); -unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start, - const char *str, int len); -unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start, - const char *str); -unsigned int aa_dfa_next(struct aa_dfa *dfa, unsigned int state, - const char c); -unsigned int aa_dfa_outofband_transition(struct aa_dfa *dfa, - unsigned int state); -unsigned int aa_dfa_match_until(struct aa_dfa *dfa, unsigned int start, - const char *str, const char **retpos); -unsigned int aa_dfa_matchn_until(struct aa_dfa *dfa, unsigned int start, - const char *str, int n, const char **retpos); +aa_state_t aa_dfa_match_len(struct aa_dfa *dfa, aa_state_t start, + const char *str, int len); +aa_state_t aa_dfa_match(struct aa_dfa *dfa, aa_state_t start, + const char *str); +aa_state_t aa_dfa_next(struct aa_dfa *dfa, aa_state_t state, const char c); +aa_state_t aa_dfa_outofband_transition(struct aa_dfa *dfa, aa_state_t state); +aa_state_t aa_dfa_match_until(struct aa_dfa *dfa, aa_state_t start, + const char *str, const char **retpos); +aa_state_t aa_dfa_matchn_until(struct aa_dfa *dfa, aa_state_t start, + const char *str, int n, const char **retpos); void aa_dfa_free_kref(struct kref *kref); @@ -156,8 +156,8 @@ struct match_workbuf N = { \ .len = 0, \ } -unsigned int aa_dfa_leftmatch(struct aa_dfa *dfa, unsigned int start, - const char *str, unsigned int *count); +aa_state_t aa_dfa_leftmatch(struct aa_dfa *dfa, aa_state_t start, + const char *str, unsigned int *count); /** * aa_get_dfa - increment refcount on dfa @p diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h index aadb4b29fb66..6fa440b5daed 100644 --- a/security/apparmor/include/net.h +++ b/security/apparmor/include/net.h @@ -59,6 +59,7 @@ struct aa_sk_ctx { DEFINE_AUDIT_DATA(NAME, \ ((SK) && (F) != AF_UNIX) ? LSM_AUDIT_DATA_NET : \ LSM_AUDIT_DATA_NONE, \ + AA_CLASS_NET, \ OP); \ NAME.u.net = &(NAME ## _net); \ aad(&NAME)->net.type = (T); \ diff --git a/security/apparmor/include/perms.h b/security/apparmor/include/perms.h index 13f20c598448..797a7a00644d 100644 --- a/security/apparmor/include/perms.h +++ b/security/apparmor/include/perms.h @@ -65,29 +65,90 @@ extern const char *aa_file_perm_names[]; struct aa_perms { u32 allow; - u32 audit; /* set only when allow is set */ - u32 deny; /* explicit deny, or conflict if allow also set */ - u32 quiet; /* set only when ~allow | deny */ - u32 kill; /* set only when ~allow | deny */ - u32 stop; /* set only when ~allow | deny */ - u32 complain; /* accumulates only used when ~allow & ~deny */ + u32 subtree; /* allow perm on full subtree only when allow is set */ u32 cond; /* set only when ~allow and ~deny */ - u32 hide; /* set only when ~allow | deny */ + u32 kill; /* set only when ~allow | deny */ + u32 complain; /* accumulates only used when ~allow & ~deny */ u32 prompt; /* accumulates only used when ~allow & ~deny */ - /* Reserved: - * u32 subtree; / * set only when allow is set * / - */ - u16 xindex; + u32 audit; /* set only when allow is set */ + u32 quiet; /* set only when ~allow | deny */ + u32 hide; /* set only when ~allow | deny */ + + + u32 xindex; + u32 tag; /* tag string index, if present */ + u32 label; /* label string index, if present */ }; +/* + * Indexes are broken into a 24 bit index and 8 bit flag. + * For the index to be valid there must be a value in the flag + */ +#define AA_INDEX_MASK 0x00ffffff +#define AA_INDEX_FLAG_MASK 0xff000000 +#define AA_INDEX_NONE 0 + #define ALL_PERMS_MASK 0xffffffff extern struct aa_perms nullperms; extern struct aa_perms allperms; +/** + * aa_perms_accum_raw - accumulate perms with out masking off overlapping perms + * @accum - perms struct to accumulate into + * @addend - perms struct to add to @accum + */ +static inline void aa_perms_accum_raw(struct aa_perms *accum, + struct aa_perms *addend) +{ + accum->deny |= addend->deny; + accum->allow &= addend->allow & ~addend->deny; + accum->audit |= addend->audit & addend->allow; + accum->quiet &= addend->quiet & ~addend->allow; + accum->kill |= addend->kill & ~addend->allow; + accum->complain |= addend->complain & ~addend->allow & ~addend->deny; + accum->cond |= addend->cond & ~addend->allow & ~addend->deny; + accum->hide &= addend->hide & ~addend->allow; + accum->prompt |= addend->prompt & ~addend->allow & ~addend->deny; + accum->subtree |= addend->subtree & ~addend->deny; + + if (!accum->xindex) + accum->xindex = addend->xindex; + if (!accum->tag) + accum->tag = addend->tag; + if (!accum->label) + accum->label = addend->label; +} + +/** + * aa_perms_accum - accumulate perms, masking off overlapping perms + * @accum - perms struct to accumulate into + * @addend - perms struct to add to @accum + */ +static inline void aa_perms_accum(struct aa_perms *accum, + struct aa_perms *addend) +{ + accum->deny |= addend->deny; + accum->allow &= addend->allow & ~accum->deny; + accum->audit |= addend->audit & accum->allow; + accum->quiet &= addend->quiet & ~accum->allow; + accum->kill |= addend->kill & ~accum->allow; + accum->complain |= addend->complain & ~accum->allow & ~accum->deny; + accum->cond |= addend->cond & ~accum->allow & ~accum->deny; + accum->hide &= addend->hide & ~accum->allow; + accum->prompt |= addend->prompt & ~accum->allow & ~accum->deny; + accum->subtree &= addend->subtree & ~accum->deny; + + if (!accum->xindex) + accum->xindex = addend->xindex; + if (!accum->tag) + accum->tag = addend->tag; + if (!accum->label) + accum->label = addend->label; +} #define xcheck(FN1, FN2) \ ({ \ @@ -133,6 +194,9 @@ extern struct aa_perms allperms; xcheck(fn_for_each((L1), (P), (FN1)), fn_for_each((L2), (P), (FN2))) +extern struct aa_perms default_perms; + + void aa_perm_mask_to_str(char *str, size_t str_size, const char *chrs, u32 mask); void aa_audit_perm_names(struct audit_buffer *ab, const char * const *names, @@ -141,11 +205,10 @@ void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs, u32 chrsmask, const char * const *names, u32 namesmask); void aa_apply_modes_to_perms(struct aa_profile *profile, struct aa_perms *perms); -void aa_compute_perms(struct aa_dfa *dfa, unsigned int state, - struct aa_perms *perms); void aa_perms_accum(struct aa_perms *accum, struct aa_perms *addend); void aa_perms_accum_raw(struct aa_perms *accum, struct aa_perms *addend); -void aa_profile_match_label(struct aa_profile *profile, struct aa_label *label, +void aa_profile_match_label(struct aa_profile *profile, + struct aa_ruleset *rules, struct aa_label *label, int type, u32 request, struct aa_perms *perms); int aa_profile_label_perm(struct aa_profile *profile, struct aa_profile *target, u32 request, int type, u32 *deny, diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index 639b5b248e63..545f791cabda 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -44,6 +44,8 @@ extern const char *const aa_profile_mode_names[]; #define COMPLAIN_MODE(_profile) PROFILE_MODE((_profile), APPARMOR_COMPLAIN) +#define USER_MODE(_profile) PROFILE_MODE((_profile), APPARMOR_USER) + #define KILL_MODE(_profile) PROFILE_MODE((_profile), APPARMOR_KILL) #define PROFILE_IS_HAT(_profile) ((_profile)->label.flags & FLAG_HAT) @@ -67,20 +69,47 @@ enum profile_mode { APPARMOR_COMPLAIN, /* allow and log access violations */ APPARMOR_KILL, /* kill task on access violation */ APPARMOR_UNCONFINED, /* profile set to unconfined */ + APPARMOR_USER, /* modified complain mode to userspace */ }; /* struct aa_policydb - match engine for a policy * 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 { - /* Generic policy DFA specific rule types will be subsections of it */ struct aa_dfa *dfa; - unsigned int start[AA_CLASS_LAST + 1]; - + struct { + struct aa_perms *perms; + u32 size; + }; + struct aa_str_table trans; + aa_state_t start[AA_CLASS_LAST + 1]; }; +static inline void aa_destroy_policydb(struct aa_policydb *policy) +{ + aa_put_dfa(policy->dfa); + if (policy->perms) + kvfree(policy->perms); + aa_free_str_table(&policy->trans); + +} + +static inline struct aa_perms *aa_lookup_perms(struct aa_policydb *policy, + aa_state_t state) +{ + unsigned int index = ACCEPT_TABLE(policy->dfa)[state]; + + if (!(policy->perms)) + return &default_perms; + + return &(policy->perms[index]); +} + + /* struct aa_data - generic data structure * key: name for retrieving this data * size: size of data in bytes @@ -94,6 +123,47 @@ struct aa_data { struct rhash_head head; }; +/* struct aa_ruleset - data covering mediation rules + * @list: list the rule is on + * @size: the memory consumed by this ruleset + * @policy: general match rules governing policy + * @file: The set of rules governing basic file access and domain transitions + * @caps: capabilities for the profile + * @rlimits: rlimits for the profile + * @secmark_count: number of secmark entries + * @secmark: secmark label match info + */ +struct aa_ruleset { + struct list_head list; + + int size; + + /* TODO: merge policy and file */ + struct aa_policydb policy; + struct aa_policydb file; + struct aa_caps caps; + + struct aa_rlimit rlimits; + + int secmark_count; + struct aa_secmark *secmark; +}; + +/* struct aa_attachment - data and rules for a profiles attachment + * @list: + * @xmatch_str: human readable attachment string + * @xmatch: optional extended matching for unconfined executables names + * @xmatch_len: xmatch prefix len, used to determine xmatch priority + * @xattr_count: number of xattrs in table + * @xattrs: table of xattrs + */ +struct aa_attachment { + const char *xmatch_str; + struct aa_policydb xmatch; + unsigned int xmatch_len; + int xattr_count; + char **xattrs; +}; /* struct aa_profile - basic confinement data * @base - base components of the profile (name, refcount, lists, lock ...) @@ -101,18 +171,13 @@ struct aa_data { * @parent: parent of profile * @ns: namespace the profile is in * @rename: optional profile name that this profile renamed - * @attach: human readable attachment string - * @xmatch: optional extended matching for unconfined executables names - * @xmatch_len: xmatch prefix len, used to determine xmatch priority + * * @audit: the auditing mode of the profile * @mode: the enforcement mode of the profile * @path_flags: flags controlling path generation behavior * @disconnected: what to prepend if attach_disconnected is specified - * @size: the memory consumed by this profiles rules - * @policy: general match rules governing policy - * @file: The set of rules governing basic file access and domain transitions - * @caps: capabilities for the profile - * @rlimits: rlimits for the profile + * @attach: attachment rules for the profile + * @rules: rules to be enforced * * @dents: dentries for the profiles file entries in apparmorfs * @dirname: name of the profile dir in apparmorfs @@ -137,26 +202,13 @@ struct aa_profile { struct aa_ns *ns; const char *rename; - const char *attach; - struct aa_dfa *xmatch; - unsigned int xmatch_len; enum audit_mode audit; long mode; u32 path_flags; const char *disconnected; - int size; - struct aa_policydb policy; - struct aa_file_rules file; - struct aa_caps caps; - - int xattr_count; - char **xattrs; - - struct aa_rlimit rlimits; - - int secmark_count; - struct aa_secmark *secmark; + struct aa_attachment attach; + struct list_head rules; struct aa_loaddata *rawdata; unsigned char *hash; @@ -179,10 +231,13 @@ 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); -struct aa_profile *aa_new_null_profile(struct aa_profile *parent, bool hat, - const char *base, gfp_t gfp); +struct aa_profile *aa_alloc_null(struct aa_profile *parent, const char *name, + gfp_t gfp); +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); @@ -217,24 +272,34 @@ static inline struct aa_profile *aa_get_newest_profile(struct aa_profile *p) return labels_profile(aa_get_newest_label(&p->label)); } -static inline unsigned int PROFILE_MEDIATES(struct aa_profile *profile, - unsigned char class) +static inline aa_state_t RULE_MEDIATES(struct aa_ruleset *rules, + unsigned char class) { if (class <= AA_CLASS_LAST) - return profile->policy.start[class]; + return rules->policy.start[class]; else - return aa_dfa_match_len(profile->policy.dfa, - profile->policy.start[0], &class, 1); + return aa_dfa_match_len(rules->policy.dfa, + rules->policy.start[0], &class, 1); } -static inline unsigned int PROFILE_MEDIATES_AF(struct aa_profile *profile, - u16 AF) { - unsigned int state = PROFILE_MEDIATES(profile, AA_CLASS_NET); +static inline aa_state_t RULE_MEDIATES_AF(struct aa_ruleset *rules, u16 AF) +{ + aa_state_t state = RULE_MEDIATES(rules, AA_CLASS_NET); __be16 be_af = cpu_to_be16(AF); if (!state) - return 0; - return aa_dfa_match_len(profile->policy.dfa, state, (char *) &be_af, 2); + return DFA_NOMATCH; + 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, + unsigned char class) +{ + struct aa_ruleset *rule; + + /* TODO: change to list walk */ + rule = list_first_entry(head, typeof(*rule), list); + return RULE_MEDIATES(rule, class); } /** diff --git a/security/apparmor/include/policy_compat.h b/security/apparmor/include/policy_compat.h new file mode 100644 index 000000000000..af0e174332df --- /dev/null +++ b/security/apparmor/include/policy_compat.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * AppArmor security module + * + * Code to provide backwards compatibility with older policy versions, + * by converting/mapping older policy formats into the newer internal + * formats. + * + * Copyright 2022 Canonical Ltd. + */ + +#ifndef __POLICY_COMPAT_H +#define __POLICY_COMPAT_H + +#include "policy.h" + +#define K_ABI_MASK 0x3ff +#define FORCE_COMPLAIN_FLAG 0x800 +#define VERSION_LT(X, Y) (((X) & K_ABI_MASK) < ((Y) & K_ABI_MASK)) +#define VERSION_LE(X, Y) (((X) & K_ABI_MASK) <= ((Y) & K_ABI_MASK)) +#define VERSION_GT(X, Y) (((X) & K_ABI_MASK) > ((Y) & K_ABI_MASK)) + +#define v5 5 /* base version */ +#define v6 6 /* per entry policydb mediation check */ +#define v7 7 +#define v8 8 /* full network masking */ +#define v9 9 /* xbits are used as permission bits in policydb */ + +int aa_compat_map_xmatch(struct aa_policydb *policy); +int aa_compat_map_policy(struct aa_policydb *policy, u32 version); +int aa_compat_map_file(struct aa_policydb *policy); + +#endif /* __POLICY_COMPAT_H */ diff --git a/security/apparmor/include/policy_unpack.h b/security/apparmor/include/policy_unpack.h index e89b701447bc..a6f4611ee50c 100644 --- a/security/apparmor/include/policy_unpack.h +++ b/security/apparmor/include/policy_unpack.h @@ -16,6 +16,7 @@ #include <linux/dcache.h> #include <linux/workqueue.h> + struct aa_load_ent { struct list_head list; struct aa_profile *new; @@ -35,6 +36,7 @@ struct aa_load_ent *aa_load_ent_alloc(void); #define PACKED_MODE_COMPLAIN 1 #define PACKED_MODE_KILL 2 #define PACKED_MODE_UNCONFINED 3 +#define PACKED_MODE_USER 4 struct aa_ns; @@ -170,7 +172,7 @@ bool aa_unpack_X(struct aa_ext *e, enum aa_code code); bool aa_unpack_nameX(struct aa_ext *e, enum aa_code code, const char *name); bool aa_unpack_u32(struct aa_ext *e, u32 *data, const char *name); bool aa_unpack_u64(struct aa_ext *e, u64 *data, const char *name); -size_t aa_unpack_array(struct aa_ext *e, const char *name); +bool aa_unpack_array(struct aa_ext *e, const char *name, u16 *size); size_t aa_unpack_blob(struct aa_ext *e, char **blob, const char *name); int aa_unpack_str(struct aa_ext *e, const char **string, const char *name); int aa_unpack_strdup(struct aa_ext *e, char **string, const char *name); diff --git a/security/apparmor/ipc.c b/security/apparmor/ipc.c index 3dbbc59d440d..5acde746775f 100644 --- a/security/apparmor/ipc.c +++ b/security/apparmor/ipc.c @@ -45,7 +45,7 @@ static const char *audit_signal_mask(u32 mask) } /** - * audit_cb - call back for signal specific audit fields + * audit_signal_cb() - call back for signal specific audit fields * @ab: audit_buffer (NOT NULL) * @va: audit struct to audit values of (NOT NULL) */ @@ -78,19 +78,21 @@ static int profile_signal_perm(struct aa_profile *profile, struct aa_label *peer, u32 request, struct common_audit_data *sa) { + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); struct aa_perms perms; - unsigned int state; + aa_state_t state; if (profile_unconfined(profile) || - !PROFILE_MEDIATES(profile, AA_CLASS_SIGNAL)) + !ANY_RULE_MEDIATES(&profile->rules, AA_CLASS_SIGNAL)) return 0; aad(sa)->peer = peer; /* TODO: secondary cache check <profile, profile, perm> */ - state = aa_dfa_next(profile->policy.dfa, - profile->policy.start[AA_CLASS_SIGNAL], + state = aa_dfa_next(rules->policy.dfa, + rules->policy.start[AA_CLASS_SIGNAL], aad(sa)->signal); - aa_label_match(profile, peer, state, false, request, &perms); + aa_label_match(profile, rules, peer, state, false, request, &perms); aa_apply_modes_to_perms(profile, &perms); return aa_check_perms(profile, &perms, request, sa, audit_signal_cb); } @@ -98,7 +100,7 @@ static int profile_signal_perm(struct aa_profile *profile, int aa_may_signal(struct aa_label *sender, struct aa_label *target, int sig) { struct aa_profile *profile; - DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_SIGNAL); + DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, AA_CLASS_SIGNAL, OP_SIGNAL); aad(&sa)->signal = map_signal_num(sig); aad(&sa)->unmappedsig = sig; diff --git a/security/apparmor/label.c b/security/apparmor/label.c index 0f36ee907438..8a2af96f4da5 100644 --- a/security/apparmor/label.c +++ b/security/apparmor/label.c @@ -197,15 +197,18 @@ static bool vec_is_stale(struct aa_profile **vec, int n) return false; } -static long union_vec_flags(struct aa_profile **vec, int n, long mask) +static long accum_vec_flags(struct aa_profile **vec, int n) { - long u = 0; + long u = FLAG_UNCONFINED; int i; AA_BUG(!vec); for (i = 0; i < n; i++) { - u |= vec[i]->label.flags & mask; + u |= vec[i]->label.flags & (FLAG_DEBUG1 | FLAG_DEBUG2 | + FLAG_STALE); + if (!(u & vec[i]->label.flags & FLAG_UNCONFINED)) + u &= ~FLAG_UNCONFINED; } return u; @@ -1097,8 +1100,7 @@ static struct aa_label *label_merge_insert(struct aa_label *new, else if (k == b->size) return aa_get_label(b); } - new->flags |= union_vec_flags(new->vec, new->size, FLAG_UNCONFINED | - FLAG_DEBUG1 | FLAG_DEBUG2); + new->flags |= accum_vec_flags(new->vec, new->size); ls = labels_set(new); write_lock_irqsave(&ls->lock, flags); label = __label_insert(labels_set(new), new, false); @@ -1254,32 +1256,27 @@ out: return label; } -static inline bool label_is_visible(struct aa_profile *profile, - struct aa_label *label) -{ - return aa_ns_visible(profile->ns, labels_ns(label), true); -} - /* match a profile and its associated ns component if needed * Assumes visibility test has already been done. * If a subns profile is not to be matched should be prescreened with * visibility test. */ -static inline unsigned int match_component(struct aa_profile *profile, - struct aa_profile *tp, - unsigned int state) +static inline aa_state_t match_component(struct aa_profile *profile, + struct aa_ruleset *rules, + struct aa_profile *tp, + aa_state_t state) { const char *ns_name; if (profile->ns == tp->ns) - return aa_dfa_match(profile->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(profile->policy.dfa, state, ":", 1); - state = aa_dfa_match(profile->policy.dfa, state, ns_name); - state = aa_dfa_match_len(profile->policy.dfa, state, ":", 1); - return aa_dfa_match(profile->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); } /** @@ -1298,8 +1295,9 @@ static inline unsigned int match_component(struct aa_profile *profile, * check to be stacked. */ static int label_compound_match(struct aa_profile *profile, + struct aa_ruleset *rules, struct aa_label *label, - unsigned int state, bool subns, u32 request, + aa_state_t state, bool subns, u32 request, struct aa_perms *perms) { struct aa_profile *tp; @@ -1309,7 +1307,7 @@ static int label_compound_match(struct aa_profile *profile, label_for_each(i, label, tp) { if (!aa_ns_visible(profile->ns, tp->ns, subns)) continue; - state = match_component(profile, tp, state); + state = match_component(profile, rules, tp, state); if (!state) goto fail; goto next; @@ -1323,12 +1321,12 @@ next: label_for_each_cont(i, label, tp) { if (!aa_ns_visible(profile->ns, tp->ns, subns)) continue; - state = aa_dfa_match(profile->policy.dfa, state, "//&"); - state = match_component(profile, tp, state); + state = aa_dfa_match(rules->policy.dfa, state, "//&"); + state = match_component(profile, rules, tp, state); if (!state) goto fail; } - aa_compute_perms(profile->policy.dfa, state, perms); + *perms = *aa_lookup_perms(&rules->policy, state); aa_apply_modes_to_perms(profile, perms); if ((perms->allow & request) != request) return -EACCES; @@ -1343,6 +1341,7 @@ fail: /** * label_components_match - find perms for all subcomponents of a label * @profile: profile to find perms for + * @rules: ruleset to search * @label: label to check access permissions for * @start: state to start match in * @subns: whether to do permission checks on components in a subns @@ -1356,20 +1355,21 @@ fail: * check to be stacked. */ static int label_components_match(struct aa_profile *profile, - struct aa_label *label, unsigned int start, + struct aa_ruleset *rules, + struct aa_label *label, aa_state_t start, bool subns, u32 request, struct aa_perms *perms) { struct aa_profile *tp; struct label_it i; struct aa_perms tmp; - unsigned int state = 0; + aa_state_t state = 0; /* find first subcomponent to test */ label_for_each(i, label, tp) { if (!aa_ns_visible(profile->ns, tp->ns, subns)) continue; - state = match_component(profile, tp, start); + state = match_component(profile, rules, tp, start); if (!state) goto fail; goto next; @@ -1379,16 +1379,16 @@ static int label_components_match(struct aa_profile *profile, return 0; next: - aa_compute_perms(profile->policy.dfa, state, &tmp); + 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) { if (!aa_ns_visible(profile->ns, tp->ns, subns)) continue; - state = match_component(profile, tp, start); + state = match_component(profile, rules, tp, start); if (!state) goto fail; - aa_compute_perms(profile->policy.dfa, state, &tmp); + tmp = *aa_lookup_perms(&rules->policy, state); aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum(perms, &tmp); } @@ -1406,6 +1406,7 @@ fail: /** * aa_label_match - do a multi-component label match * @profile: profile to match against (NOT NULL) + * @rules: ruleset to search * @label: label to match (NOT NULL) * @state: state to start in * @subns: whether to match subns components @@ -1414,18 +1415,18 @@ fail: * * Returns: the state the match finished in, may be the none matching state */ -int aa_label_match(struct aa_profile *profile, struct aa_label *label, - unsigned int state, bool subns, u32 request, - struct aa_perms *perms) +int aa_label_match(struct aa_profile *profile, struct aa_ruleset *rules, + struct aa_label *label, aa_state_t state, bool subns, + u32 request, struct aa_perms *perms) { - int error = label_compound_match(profile, label, state, subns, request, - perms); + int error = label_compound_match(profile, rules, label, state, subns, + request, perms); if (!error) return error; *perms = allperms; - return label_components_match(profile, label, state, subns, request, - perms); + return label_components_match(profile, rules, label, state, subns, + request, perms); } diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index 1c72a61108d3..a630c951bb3b 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -26,6 +26,25 @@ struct aa_perms allperms = { .allow = ALL_PERMS_MASK, .hide = ALL_PERMS_MASK }; /** + * aa_free_str_table - free entries str table + * @str: the string table to free (MAYBE NULL) + */ +void aa_free_str_table(struct aa_str_table *t) +{ + int i; + + if (t) { + if (!t->table) + return; + + for (i = 0; i < t->size; i++) + kfree_sensitive(t->table[i]); + kfree_sensitive(t->table); + t->table = NULL; + } +} + +/** * aa_split_fqname - split a fqname into a profile and namespace name * @fqname: a full qualified name in namespace profile format (NOT NULL) * @ns_name: pointer to portion of the string containing the ns name (NOT NULL) @@ -124,7 +143,7 @@ const char *aa_splitn_fqname(const char *fqname, size_t n, const char **ns_name, void aa_info_message(const char *str) { if (audit_enabled) { - DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, NULL); + DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, AA_CLASS_NONE, NULL); aad(&sa)->info = str; aa_audit_msg(AUDIT_APPARMOR_STATUS, &sa, NULL); @@ -308,103 +327,22 @@ void aa_apply_modes_to_perms(struct aa_profile *profile, struct aa_perms *perms) perms->kill = ALL_PERMS_MASK; else if (COMPLAIN_MODE(profile)) perms->complain = ALL_PERMS_MASK; -/* - * TODO: - * else if (PROMPT_MODE(profile)) - * perms->prompt = ALL_PERMS_MASK; - */ -} - -static u32 map_other(u32 x) -{ - return ((x & 0x3) << 8) | /* SETATTR/GETATTR */ - ((x & 0x1c) << 18) | /* ACCEPT/BIND/LISTEN */ - ((x & 0x60) << 19); /* SETOPT/GETOPT */ -} - -static u32 map_xbits(u32 x) -{ - return ((x & 0x1) << 7) | - ((x & 0x7e) << 9); -} - -void aa_compute_perms(struct aa_dfa *dfa, unsigned int state, - struct aa_perms *perms) -{ - /* This mapping is convulated due to history. - * v1-v4: only file perms - * v5: added policydb which dropped in perm user conditional to - * gain new perm bits, but had to map around the xbits because - * the userspace compiler was still munging them. - * v9: adds using the xbits in policydb because the compiler now - * supports treating policydb permission bits different. - * Unfortunately there is not way to force auditing on the - * perms represented by the xbits - */ - *perms = (struct aa_perms) { - .allow = dfa_user_allow(dfa, state) | - map_xbits(dfa_user_xbits(dfa, state)), - .audit = dfa_user_audit(dfa, state), - .quiet = dfa_user_quiet(dfa, state) | - map_xbits(dfa_other_xbits(dfa, state)), - }; - - /* for v5-v9 perm mapping in the policydb, the other set is used - * to extend the general perm set - */ - perms->allow |= map_other(dfa_other_allow(dfa, state)); - perms->audit |= map_other(dfa_other_audit(dfa, state)); - perms->quiet |= map_other(dfa_other_quiet(dfa, state)); -} - -/** - * aa_perms_accum_raw - accumulate perms with out masking off overlapping perms - * @accum - perms struct to accumulate into - * @addend - perms struct to add to @accum - */ -void aa_perms_accum_raw(struct aa_perms *accum, struct aa_perms *addend) -{ - accum->deny |= addend->deny; - accum->allow &= addend->allow & ~addend->deny; - accum->audit |= addend->audit & addend->allow; - accum->quiet &= addend->quiet & ~addend->allow; - accum->kill |= addend->kill & ~addend->allow; - accum->stop |= addend->stop & ~addend->allow; - accum->complain |= addend->complain & ~addend->allow & ~addend->deny; - accum->cond |= addend->cond & ~addend->allow & ~addend->deny; - accum->hide &= addend->hide & ~addend->allow; - accum->prompt |= addend->prompt & ~addend->allow & ~addend->deny; -} - -/** - * aa_perms_accum - accumulate perms, masking off overlapping perms - * @accum - perms struct to accumulate into - * @addend - perms struct to add to @accum - */ -void aa_perms_accum(struct aa_perms *accum, struct aa_perms *addend) -{ - accum->deny |= addend->deny; - accum->allow &= addend->allow & ~accum->deny; - accum->audit |= addend->audit & accum->allow; - accum->quiet &= addend->quiet & ~accum->allow; - accum->kill |= addend->kill & ~accum->allow; - accum->stop |= addend->stop & ~accum->allow; - accum->complain |= addend->complain & ~accum->allow & ~accum->deny; - accum->cond |= addend->cond & ~accum->allow & ~accum->deny; - accum->hide &= addend->hide & ~accum->allow; - accum->prompt |= addend->prompt & ~accum->allow & ~accum->deny; + else if (USER_MODE(profile)) + perms->prompt = ALL_PERMS_MASK; } -void aa_profile_match_label(struct aa_profile *profile, struct aa_label *label, +void aa_profile_match_label(struct aa_profile *profile, + struct aa_ruleset *rules, + struct aa_label *label, int type, u32 request, struct aa_perms *perms) { /* TODO: doesn't yet handle extended types */ - unsigned int state; + aa_state_t state; - state = aa_dfa_next(profile->policy.dfa, - profile->policy.start[AA_CLASS_LABEL], + state = aa_dfa_next(rules->policy.dfa, + rules->policy.start[AA_CLASS_LABEL], type); - aa_label_match(profile, label, state, false, request, perms); + aa_label_match(profile, rules, label, state, false, request, perms); } @@ -413,13 +351,16 @@ int aa_profile_label_perm(struct aa_profile *profile, struct aa_profile *target, u32 request, int type, u32 *deny, struct common_audit_data *sa) { + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); struct aa_perms perms; aad(sa)->label = &profile->label; aad(sa)->peer = &target->label; aad(sa)->request = request; - aa_profile_match_label(profile, &target->label, type, request, &perms); + aa_profile_match_label(profile, rules, &target->label, type, request, + &perms); aa_apply_modes_to_perms(profile, &perms); *deny |= request & perms.deny; return aa_check_perms(profile, &perms, request, sa, aa_audit_perms_cb); diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index f34675f7c3df..c6728a629437 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -21,7 +21,7 @@ #include <linux/user_namespace.h> #include <linux/netfilter_ipv4.h> #include <linux/netfilter_ipv6.h> -#include <linux/zlib.h> +#include <linux/zstd.h> #include <net/sock.h> #include <uapi/linux/mount.h> @@ -163,12 +163,15 @@ static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective, struct label_it i; label_for_each_confined(i, label, profile) { + struct aa_ruleset *rules; if (COMPLAIN_MODE(profile)) continue; + rules = list_first_entry(&profile->rules, + typeof(*rules), list); *effective = cap_intersect(*effective, - profile->caps.allow); + rules->caps.allow); *permitted = cap_intersect(*permitted, - profile->caps.allow); + rules->caps.allow); } } rcu_read_unlock(); @@ -333,6 +336,11 @@ static int apparmor_path_truncate(const struct path *path) return common_perm_cond(OP_TRUNC, path, MAY_WRITE | AA_MAY_SETATTR); } +static int apparmor_file_truncate(struct file *file) +{ + return apparmor_path_truncate(&file->f_path); +} + static int apparmor_path_symlink(const struct path *dir, struct dentry *dentry, const char *old_name) { @@ -656,7 +664,8 @@ static int apparmor_setprocattr(const char *name, void *value, char *command, *largs = NULL, *args = value; size_t arg_size; int error; - DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_SETPROCATTR); + DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, AA_CLASS_NONE, + OP_SETPROCATTR); if (size == 0) return -EINVAL; @@ -746,7 +755,7 @@ static void apparmor_bprm_committing_creds(struct linux_binprm *bprm) } /** - * apparmor_bprm_committed_cred - do cleanup after new creds committed + * 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) @@ -1112,11 +1121,10 @@ static struct aa_label *sk_peer_label(struct sock *sk) * Note: for tcp only valid if using ipsec or cipso on lan */ static int apparmor_socket_getpeersec_stream(struct socket *sock, - char __user *optval, - int __user *optlen, + sockptr_t optval, sockptr_t optlen, unsigned int len) { - char *name; + char *name = NULL; int slen, error = 0; struct aa_label *label; struct aa_label *peer; @@ -1133,23 +1141,21 @@ static int apparmor_socket_getpeersec_stream(struct socket *sock, /* don't include terminating \0 in slen, it breaks some apps */ if (slen < 0) { error = -ENOMEM; - } else { - if (slen > len) { - error = -ERANGE; - } else if (copy_to_user(optval, name, slen)) { - error = -EFAULT; - goto out; - } - if (put_user(slen, optlen)) - error = -EFAULT; -out: - kfree(name); - + goto done; + } + if (slen > len) { + error = -ERANGE; + goto done_len; } + if (copy_to_sockptr(optval, name, slen)) + error = -EFAULT; +done_len: + if (copy_to_sockptr(optlen, &slen, sizeof(slen))) + error = -EFAULT; done: end_current_label_crit_section(label); - + kfree(name); return error; } @@ -1203,10 +1209,10 @@ static int apparmor_inet_conn_request(const struct sock *sk, struct sk_buff *skb #endif /* - * The cred blob is a pointer to, not an instance of, an aa_task_ctx. + * The cred blob is a pointer to, not an instance of, an aa_label. */ struct lsm_blob_sizes apparmor_blob_sizes __lsm_ro_after_init = { - .lbs_cred = sizeof(struct aa_task_ctx *), + .lbs_cred = sizeof(struct aa_label *), .lbs_file = sizeof(struct aa_file_ctx), .lbs_task = sizeof(struct aa_task_ctx), }; @@ -1241,6 +1247,7 @@ static struct security_hook_list apparmor_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(mmap_file, apparmor_mmap_file), LSM_HOOK_INIT(file_mprotect, apparmor_file_mprotect), LSM_HOOK_INIT(file_lock, apparmor_file_lock), + LSM_HOOK_INIT(file_truncate, apparmor_file_truncate), LSM_HOOK_INIT(getprocattr, apparmor_getprocattr), LSM_HOOK_INIT(setprocattr, apparmor_setprocattr), @@ -1370,7 +1377,7 @@ module_param_named(export_binary, aa_g_export_binary, aabool, 0600); #endif /* policy loaddata compression level */ -int aa_g_rawdata_compression_level = Z_DEFAULT_COMPRESSION; +int aa_g_rawdata_compression_level = AA_DEFAULT_CLEVEL; module_param_named(rawdata_compression_level, aa_g_rawdata_compression_level, aacompressionlevel, 0400); @@ -1552,9 +1559,8 @@ static int param_set_aacompressionlevel(const char *val, error = param_set_int(val, kp); aa_g_rawdata_compression_level = clamp(aa_g_rawdata_compression_level, - Z_NO_COMPRESSION, - Z_BEST_COMPRESSION); - pr_info("AppArmor: policy rawdata compression level set to %u\n", + AA_MIN_CLEVEL, AA_MAX_CLEVEL); + pr_info("AppArmor: policy rawdata compression level set to %d\n", aa_g_rawdata_compression_level); return error; diff --git a/security/apparmor/match.c b/security/apparmor/match.c index 3e9e1eaf990e..b97ef5e1db73 100644 --- a/security/apparmor/match.c +++ b/security/apparmor/match.c @@ -31,7 +31,7 @@ static char stacksplitdfa_src[] = { }; struct aa_dfa *stacksplitdfa; -int aa_setup_dfa_engine(void) +int __init aa_setup_dfa_engine(void) { int error; @@ -59,7 +59,7 @@ int aa_setup_dfa_engine(void) return 0; } -void aa_teardown_dfa_engine(void) +void __init aa_teardown_dfa_engine(void) { aa_put_dfa(stacksplitdfa); aa_put_dfa(nulldfa); @@ -436,17 +436,17 @@ do { \ * * Returns: final state reached after input is consumed */ -unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start, - const char *str, int len) +aa_state_t aa_dfa_match_len(struct aa_dfa *dfa, aa_state_t start, + const char *str, int len) { u16 *def = DEFAULT_TABLE(dfa); u32 *base = BASE_TABLE(dfa); u16 *next = NEXT_TABLE(dfa); u16 *check = CHECK_TABLE(dfa); - unsigned int state = start; + aa_state_t state = start; - if (state == 0) - return 0; + if (state == DFA_NOMATCH) + return DFA_NOMATCH; /* current state is <state>, matching character *str */ if (dfa->tables[YYTD_ID_EC]) { @@ -476,17 +476,16 @@ unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start, * * Returns: final state reached after input is consumed */ -unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start, - const char *str) +aa_state_t aa_dfa_match(struct aa_dfa *dfa, aa_state_t start, const char *str) { u16 *def = DEFAULT_TABLE(dfa); u32 *base = BASE_TABLE(dfa); u16 *next = NEXT_TABLE(dfa); u16 *check = CHECK_TABLE(dfa); - unsigned int state = start; + aa_state_t state = start; - if (state == 0) - return 0; + if (state == DFA_NOMATCH) + return DFA_NOMATCH; /* current state is <state>, matching character *str */ if (dfa->tables[YYTD_ID_EC]) { @@ -515,8 +514,7 @@ unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start, * * Returns: state reach after input @c */ -unsigned int aa_dfa_next(struct aa_dfa *dfa, unsigned int state, - const char c) +aa_state_t aa_dfa_next(struct aa_dfa *dfa, aa_state_t state, const char c) { u16 *def = DEFAULT_TABLE(dfa); u32 *base = BASE_TABLE(dfa); @@ -534,7 +532,7 @@ unsigned int aa_dfa_next(struct aa_dfa *dfa, unsigned int state, return state; } -unsigned int aa_dfa_outofband_transition(struct aa_dfa *dfa, unsigned int state) +aa_state_t aa_dfa_outofband_transition(struct aa_dfa *dfa, aa_state_t state) { u16 *def = DEFAULT_TABLE(dfa); u32 *base = BASE_TABLE(dfa); @@ -564,7 +562,7 @@ unsigned int aa_dfa_outofband_transition(struct aa_dfa *dfa, unsigned int state) * * Returns: final state reached after input is consumed */ -unsigned int aa_dfa_match_until(struct aa_dfa *dfa, unsigned int start, +aa_state_t aa_dfa_match_until(struct aa_dfa *dfa, aa_state_t start, const char *str, const char **retpos) { u16 *def = DEFAULT_TABLE(dfa); @@ -572,10 +570,10 @@ unsigned int aa_dfa_match_until(struct aa_dfa *dfa, unsigned int start, u16 *next = NEXT_TABLE(dfa); u16 *check = CHECK_TABLE(dfa); u32 *accept = ACCEPT_TABLE(dfa); - unsigned int state = start, pos; + aa_state_t state = start, pos; - if (state == 0) - return 0; + if (state == DFA_NOMATCH) + return DFA_NOMATCH; /* current state is <state>, matching character *str */ if (dfa->tables[YYTD_ID_EC]) { @@ -625,7 +623,7 @@ unsigned int aa_dfa_match_until(struct aa_dfa *dfa, unsigned int start, * * Returns: final state reached after input is consumed */ -unsigned int aa_dfa_matchn_until(struct aa_dfa *dfa, unsigned int start, +aa_state_t aa_dfa_matchn_until(struct aa_dfa *dfa, aa_state_t start, const char *str, int n, const char **retpos) { u16 *def = DEFAULT_TABLE(dfa); @@ -633,11 +631,11 @@ unsigned int aa_dfa_matchn_until(struct aa_dfa *dfa, unsigned int start, u16 *next = NEXT_TABLE(dfa); u16 *check = CHECK_TABLE(dfa); u32 *accept = ACCEPT_TABLE(dfa); - unsigned int state = start, pos; + aa_state_t state = start, pos; *retpos = NULL; - if (state == 0) - return 0; + if (state == DFA_NOMATCH) + return DFA_NOMATCH; /* current state is <state>, matching character *str */ if (dfa->tables[YYTD_ID_EC]) { @@ -677,11 +675,11 @@ do { \ } while (0) /* For DFAs that don't support extended tagging of states */ -static bool is_loop(struct match_workbuf *wb, unsigned int state, +static bool is_loop(struct match_workbuf *wb, aa_state_t state, unsigned int *adjust) { - unsigned int pos = wb->pos; - unsigned int i; + aa_state_t pos = wb->pos; + aa_state_t i; if (wb->history[pos] < state) return false; @@ -700,7 +698,7 @@ static bool is_loop(struct match_workbuf *wb, unsigned int state, return true; } -static unsigned int leftmatch_fb(struct aa_dfa *dfa, unsigned int start, +static aa_state_t leftmatch_fb(struct aa_dfa *dfa, aa_state_t start, const char *str, struct match_workbuf *wb, unsigned int *count) { @@ -708,7 +706,7 @@ static unsigned int leftmatch_fb(struct aa_dfa *dfa, unsigned int start, u32 *base = BASE_TABLE(dfa); u16 *next = NEXT_TABLE(dfa); u16 *check = CHECK_TABLE(dfa); - unsigned int state = start, pos; + aa_state_t state = start, pos; AA_BUG(!dfa); AA_BUG(!str); @@ -716,8 +714,8 @@ static unsigned int leftmatch_fb(struct aa_dfa *dfa, unsigned int start, AA_BUG(!count); *count = 0; - if (state == 0) - return 0; + if (state == DFA_NOMATCH) + return DFA_NOMATCH; /* current state is <state>, matching character *str */ if (dfa->tables[YYTD_ID_EC]) { @@ -781,8 +779,8 @@ out: * * Returns: final state reached after input is consumed */ -unsigned int aa_dfa_leftmatch(struct aa_dfa *dfa, unsigned int start, - const char *str, unsigned int *count) +aa_state_t aa_dfa_leftmatch(struct aa_dfa *dfa, aa_state_t start, + const char *str, unsigned int *count) { DEFINE_MATCH_WB(wb); diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c index f61247241803..cdfa430ae216 100644 --- a/security/apparmor/mount.c +++ b/security/apparmor/mount.c @@ -134,7 +134,7 @@ static int audit_mount(struct aa_profile *profile, const char *op, struct aa_perms *perms, const char *info, int error) { int audit_type = AUDIT_APPARMOR_AUTO; - DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, op); + DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, AA_CLASS_MOUNT, op); if (likely(!error)) { u32 mask = perms->audit; @@ -190,7 +190,7 @@ static int audit_mount(struct aa_profile *profile, const char *op, * * Returns: next state after flags match */ -static unsigned int match_mnt_flags(struct aa_dfa *dfa, unsigned int state, +static aa_state_t match_mnt_flags(struct aa_dfa *dfa, aa_state_t state, unsigned long flags) { unsigned int i; @@ -203,25 +203,6 @@ static unsigned int match_mnt_flags(struct aa_dfa *dfa, unsigned int state, return state; } -/** - * compute_mnt_perms - compute mount permission associated with @state - * @dfa: dfa to match against (NOT NULL) - * @state: state match finished in - * - * Returns: mount permissions - */ -static struct aa_perms compute_mnt_perms(struct aa_dfa *dfa, - unsigned int state) -{ - struct aa_perms perms = { - .allow = dfa_user_allow(dfa, state), - .audit = dfa_user_audit(dfa, state), - .quiet = dfa_user_quiet(dfa, state), - }; - - return perms; -} - static const char * const mnt_info_table[] = { "match succeeded", "failed mntpnt match", @@ -236,50 +217,52 @@ static const char * const mnt_info_table[] = { * Returns 0 on success else element that match failed in, this is the * index into the mnt_info_table above */ -static int do_match_mnt(struct aa_dfa *dfa, unsigned int start, +static int do_match_mnt(struct aa_policydb *policy, aa_state_t start, const char *mntpnt, const char *devname, const char *type, unsigned long flags, void *data, bool binary, struct aa_perms *perms) { - unsigned int state; + aa_state_t state; - AA_BUG(!dfa); + AA_BUG(!policy); + AA_BUG(!policy->dfa); + AA_BUG(!policy->perms); AA_BUG(!perms); - state = aa_dfa_match(dfa, start, mntpnt); - state = aa_dfa_null_transition(dfa, state); + state = aa_dfa_match(policy->dfa, start, mntpnt); + state = aa_dfa_null_transition(policy->dfa, state); if (!state) return 1; if (devname) - state = aa_dfa_match(dfa, state, devname); - state = aa_dfa_null_transition(dfa, state); + state = aa_dfa_match(policy->dfa, state, devname); + state = aa_dfa_null_transition(policy->dfa, state); if (!state) return 2; if (type) - state = aa_dfa_match(dfa, state, type); - state = aa_dfa_null_transition(dfa, state); + state = aa_dfa_match(policy->dfa, state, type); + state = aa_dfa_null_transition(policy->dfa, state); if (!state) return 3; - state = match_mnt_flags(dfa, state, flags); + state = match_mnt_flags(policy->dfa, state, flags); if (!state) return 4; - *perms = compute_mnt_perms(dfa, state); + *perms = *aa_lookup_perms(policy, state); if (perms->allow & AA_MAY_MOUNT) return 0; /* only match data if not binary and the DFA flags data is expected */ if (data && !binary && (perms->allow & AA_MNT_CONT_MATCH)) { - state = aa_dfa_null_transition(dfa, state); + state = aa_dfa_null_transition(policy->dfa, state); if (!state) return 4; - state = aa_dfa_match(dfa, state, data); + state = aa_dfa_match(policy->dfa, state, data); if (!state) return 5; - *perms = compute_mnt_perms(dfa, state); + *perms = *aa_lookup_perms(policy, state); if (perms->allow & AA_MAY_MOUNT) return 0; } @@ -320,13 +303,15 @@ static int match_mnt_path_str(struct aa_profile *profile, { struct aa_perms perms = { }; const char *mntpnt = NULL, *info = NULL; + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); int pos, error; AA_BUG(!profile); AA_BUG(!mntpath); AA_BUG(!buffer); - if (!PROFILE_MEDIATES(profile, AA_CLASS_MOUNT)) + if (!RULE_MEDIATES(rules, AA_CLASS_MOUNT)) return 0; error = aa_path_name(mntpath, path_flags(profile, mntpath), buffer, @@ -341,8 +326,8 @@ static int match_mnt_path_str(struct aa_profile *profile, } error = -EACCES; - pos = do_match_mnt(profile->policy.dfa, - profile->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]; @@ -375,12 +360,14 @@ static int match_mnt(struct aa_profile *profile, const struct path *path, bool binary) { const char *devname = NULL, *info = NULL; + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); int error = -EACCES; AA_BUG(!profile); AA_BUG(devpath && !devbuffer); - if (!PROFILE_MEDIATES(profile, AA_CLASS_MOUNT)) + if (!RULE_MEDIATES(rules, AA_CLASS_MOUNT)) return 0; if (devpath) { @@ -582,15 +569,17 @@ out: static int profile_umount(struct aa_profile *profile, const struct path *path, char *buffer) { + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); struct aa_perms perms = { }; const char *name = NULL, *info = NULL; - unsigned int state; + aa_state_t state; int error; AA_BUG(!profile); AA_BUG(!path); - if (!PROFILE_MEDIATES(profile, AA_CLASS_MOUNT)) + if (!RULE_MEDIATES(rules, AA_CLASS_MOUNT)) return 0; error = aa_path_name(path, path_flags(profile, path), buffer, &name, @@ -598,10 +587,10 @@ static int profile_umount(struct aa_profile *profile, const struct path *path, if (error) goto audit; - state = aa_dfa_match(profile->policy.dfa, - profile->policy.start[AA_CLASS_MOUNT], + state = aa_dfa_match(rules->policy.dfa, + rules->policy.start[AA_CLASS_MOUNT], name); - perms = compute_mnt_perms(profile->policy.dfa, state); + perms = *aa_lookup_perms(&rules->policy, state); if (AA_MAY_UMOUNT & ~perms.allow) error = -EACCES; @@ -641,10 +630,12 @@ static struct aa_label *build_pivotroot(struct aa_profile *profile, const struct path *old_path, char *old_buffer) { + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); const char *old_name, *new_name = NULL, *info = NULL; const char *trans_name = NULL; struct aa_perms perms = { }; - unsigned int state; + aa_state_t state; int error; AA_BUG(!profile); @@ -652,7 +643,7 @@ static struct aa_label *build_pivotroot(struct aa_profile *profile, AA_BUG(!old_path); if (profile_unconfined(profile) || - !PROFILE_MEDIATES(profile, AA_CLASS_MOUNT)) + !RULE_MEDIATES(rules, AA_CLASS_MOUNT)) return aa_get_newest_label(&profile->label); error = aa_path_name(old_path, path_flags(profile, old_path), @@ -667,12 +658,12 @@ static struct aa_label *build_pivotroot(struct aa_profile *profile, goto audit; error = -EACCES; - state = aa_dfa_match(profile->policy.dfa, - profile->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(profile->policy.dfa, state); - state = aa_dfa_match(profile->policy.dfa, state, old_name); - perms = compute_mnt_perms(profile->policy.dfa, 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 7efe4d17273d..788be1609a86 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -108,8 +108,10 @@ void audit_net_cb(struct audit_buffer *ab, void *va) int aa_profile_af_perm(struct aa_profile *profile, struct common_audit_data *sa, u32 request, u16 family, int type) { + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); struct aa_perms perms = { }; - unsigned int state; + aa_state_t state; __be16 buffer[2]; AA_BUG(family >= AF_MAX); @@ -117,15 +119,15 @@ int aa_profile_af_perm(struct aa_profile *profile, struct common_audit_data *sa, if (profile_unconfined(profile)) return 0; - state = PROFILE_MEDIATES(profile, AA_CLASS_NET); + state = RULE_MEDIATES(rules, AA_CLASS_NET); if (!state) return 0; buffer[0] = cpu_to_be16(family); buffer[1] = cpu_to_be16((u16) type); - state = aa_dfa_match_len(profile->policy.dfa, state, (char *) &buffer, + state = aa_dfa_match_len(rules->policy.dfa, state, (char *) &buffer, 4); - aa_compute_perms(profile->policy.dfa, state, &perms); + perms = *aa_lookup_perms(&rules->policy, state); aa_apply_modes_to_perms(profile, &perms); return aa_check_perms(profile, &perms, request, sa, audit_net_cb); @@ -216,25 +218,27 @@ static int aa_secmark_perm(struct aa_profile *profile, u32 request, u32 secid, { int i, ret; struct aa_perms perms = { }; + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); - if (profile->secmark_count == 0) + if (rules->secmark_count == 0) return 0; - for (i = 0; i < profile->secmark_count; i++) { - if (!profile->secmark[i].secid) { - ret = apparmor_secmark_init(&profile->secmark[i]); + for (i = 0; i < rules->secmark_count; i++) { + if (!rules->secmark[i].secid) { + ret = apparmor_secmark_init(&rules->secmark[i]); if (ret) return ret; } - if (profile->secmark[i].secid == secid || - profile->secmark[i].secid == AA_SECID_WILDCARD) { - if (profile->secmark[i].deny) + if (rules->secmark[i].secid == secid || + rules->secmark[i].secid == AA_SECID_WILDCARD) { + if (rules->secmark[i].deny) perms.deny = ALL_PERMS_MASK; else perms.allow = ALL_PERMS_MASK; - if (profile->secmark[i].audit) + if (rules->secmark[i].audit) perms.audit = ALL_PERMS_MASK; } } diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 499c0209b6a4..51e8184e0fec 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -94,6 +94,7 @@ const char *const aa_profile_mode_names[] = { "complain", "kill", "unconfined", + "user", }; @@ -192,6 +193,42 @@ static void aa_free_data(void *ptr, void *arg) kfree_sensitive(data); } +static void free_attachment(struct aa_attachment *attach) +{ + int i; + + for (i = 0; i < attach->xattr_count; i++) + kfree_sensitive(attach->xattrs[i]); + kfree_sensitive(attach->xattrs); + aa_destroy_policydb(&attach->xmatch); +} + +static void free_ruleset(struct aa_ruleset *rules) +{ + int i; + + aa_destroy_policydb(&rules->file); + aa_destroy_policydb(&rules->policy); + aa_free_cap_rules(&rules->caps); + aa_free_rlimit_rules(&rules->rlimits); + + for (i = 0; i < rules->secmark_count; i++) + kfree_sensitive(rules->secmark[i].label); + kfree_sensitive(rules->secmark); + kfree_sensitive(rules); +} + +struct aa_ruleset *aa_alloc_ruleset(gfp_t gfp) +{ + struct aa_ruleset *rules; + + rules = kzalloc(sizeof(*rules), gfp); + if (rules) + INIT_LIST_HEAD(&rules->list); + + return rules; +} + /** * aa_free_profile - free a profile * @profile: the profile to free (MAYBE NULL) @@ -204,8 +241,8 @@ static void aa_free_data(void *ptr, void *arg) */ void aa_free_profile(struct aa_profile *profile) { + struct aa_ruleset *rule, *tmp; struct rhashtable *rht; - int i; AA_DEBUG("%s(%p)\n", __func__, profile); @@ -219,19 +256,17 @@ void aa_free_profile(struct aa_profile *profile) aa_put_ns(profile->ns); kfree_sensitive(profile->rename); - aa_free_file_rules(&profile->file); - aa_free_cap_rules(&profile->caps); - aa_free_rlimit_rules(&profile->rlimits); + free_attachment(&profile->attach); - for (i = 0; i < profile->xattr_count; i++) - kfree_sensitive(profile->xattrs[i]); - kfree_sensitive(profile->xattrs); - for (i = 0; i < profile->secmark_count; i++) - kfree_sensitive(profile->secmark[i].label); - kfree_sensitive(profile->secmark); + /* + * at this point there are no tasks that can have a reference + * to rules + */ + list_for_each_entry_safe(rule, tmp, &profile->rules, list) { + list_del_init(&rule->list); + free_ruleset(rule); + } kfree_sensitive(profile->dirname); - aa_put_dfa(profile->xmatch); - aa_put_dfa(profile->policy.dfa); if (profile->data) { rht = profile->data; @@ -258,6 +293,7 @@ struct aa_profile *aa_alloc_profile(const char *hname, struct aa_proxy *proxy, gfp_t gfp) { struct aa_profile *profile; + struct aa_ruleset *rules; /* freed by free_profile - usually through aa_put_profile */ profile = kzalloc(struct_size(profile, label.vec, 2), gfp); @@ -269,6 +305,14 @@ struct aa_profile *aa_alloc_profile(const char *hname, struct aa_proxy *proxy, if (!aa_label_init(&profile->label, 1, gfp)) goto fail; + INIT_LIST_HEAD(&profile->rules); + + /* allocate the first ruleset, but leave it empty */ + rules = aa_alloc_ruleset(gfp); + if (!rules) + goto fail; + list_add(&rules->list, &profile->rules); + /* update being set needed by fs interface */ if (!proxy) { proxy = aa_alloc_proxy(&profile->label, gfp); @@ -381,6 +425,57 @@ static struct aa_policy *__lookup_parent(struct aa_ns *ns, } /** + * __create_missing_ancestors - create place holders for missing ancestores + * @ns: namespace to lookup profile in (NOT NULL) + * @hname: hierarchical profile name to find parent of (NOT NULL) + * @gfp: type of allocation. + * + * Returns: NULL on error, parent profile on success + * + * Requires: ns mutex lock held + * + * Returns: unrefcounted parent policy or NULL if error creating + * place holder profiles. + */ +static struct aa_policy *__create_missing_ancestors(struct aa_ns *ns, + const char *hname, + gfp_t gfp) +{ + struct aa_policy *policy; + struct aa_profile *parent, *profile = NULL; + char *split; + + AA_BUG(!ns); + AA_BUG(!hname); + + policy = &ns->base; + + for (split = strstr(hname, "//"); split;) { + parent = profile; + profile = __strn_find_child(&policy->profiles, hname, + split - hname); + if (!profile) { + const char *name = kstrndup(hname, split - hname, + gfp); + if (!name) + return NULL; + profile = aa_alloc_null(parent, name, gfp); + kfree(name); + if (!profile) + return NULL; + if (!parent) + profile->ns = aa_get_ns(ns); + } + policy = &profile->base; + hname = split + 2; + split = strstr(hname, "//"); + } + if (!profile) + return &ns->base; + return &profile->base; +} + +/** * __lookupn_profile - lookup the profile matching @hname * @base: base list to start looking up profile name from (NOT NULL) * @hname: hierarchical profile name (NOT NULL) @@ -481,8 +576,36 @@ struct aa_profile *aa_fqlookupn_profile(struct aa_label *base, return profile; } + +struct aa_profile *aa_alloc_null(struct aa_profile *parent, const char *name, + gfp_t gfp) +{ + struct aa_profile *profile; + struct aa_ruleset *rules; + + profile = aa_alloc_profile(name, NULL, gfp); + if (!profile) + return NULL; + + /* 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->policy.dfa = aa_get_dfa(nulldfa); + + if (parent) { + profile->path_flags = parent->path_flags; + + /* released on free_profile */ + rcu_assign_pointer(profile->parent, aa_get_profile(parent)); + profile->ns = aa_get_ns(parent->ns); + } + + return profile; +} + /** - * aa_new_null_profile - create or find a null-X learning profile + * aa_new_learning_profile - create or find a null-X learning profile * @parent: profile that caused this profile to be created (NOT NULL) * @hat: true if the null- learning profile is a hat * @base: name to base the null profile off of @@ -499,8 +622,8 @@ struct aa_profile *aa_fqlookupn_profile(struct aa_label *base, * * Returns: new refcounted profile else NULL on failure */ -struct aa_profile *aa_new_null_profile(struct aa_profile *parent, bool hat, - const char *base, gfp_t gfp) +struct aa_profile *aa_new_learning_profile(struct aa_profile *parent, bool hat, + const char *base, gfp_t gfp) { struct aa_profile *p, *profile; const char *bname; @@ -531,21 +654,12 @@ name: if (profile) goto out; - profile = aa_alloc_profile(name, NULL, gfp); + profile = aa_alloc_null(parent, name, gfp); if (!profile) goto fail; - profile->mode = APPARMOR_COMPLAIN; - profile->label.flags |= FLAG_NULL; if (hat) profile->label.flags |= FLAG_HAT; - profile->path_flags = parent->path_flags; - - /* released on free_profile */ - rcu_assign_pointer(profile->parent, aa_get_profile(parent)); - profile->ns = aa_get_ns(parent->ns); - profile->file.dfa = aa_get_dfa(nulldfa); - profile->policy.dfa = aa_get_dfa(nulldfa); mutex_lock_nested(&profile->ns->lock, profile->ns->level); p = __find_child(&parent->base.profiles, bname); @@ -618,7 +732,7 @@ static int audit_policy(struct aa_label *label, const char *op, const char *ns_name, const char *name, const char *info, int error) { - DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, op); + DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, AA_CLASS_NONE, op); aad(&sa)->iface.ns = ns_name; aad(&sa)->name = name; @@ -970,6 +1084,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label, /* setup parent and ns info */ list_for_each_entry(ent, &lh, list) { struct aa_policy *policy; + struct aa_profile *p; if (aa_g_export_binary) ent->new->rawdata = aa_get_loaddata(udata); @@ -994,21 +1109,38 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label, continue; /* no ref on policy only use inside lock */ + p = NULL; policy = __lookup_parent(ns, ent->new->base.hname); if (!policy) { - struct aa_profile *p; + /* first check for parent in the load set */ p = __list_lookup_parent(&lh, ent->new); if (!p) { - error = -ENOENT; - info = "parent does not exist"; - goto fail_lock; + /* + * fill in missing parent with null + * profile that doesn't have + * permissions. This allows for + * individual profile loading where + * the child is loaded before the + * parent, and outside of the current + * atomic set. This unfortunately can + * happen with some userspaces. The + * null profile will be replaced once + * the parent is loaded. + */ + policy = __create_missing_ancestors(ns, + ent->new->base.hname, + GFP_KERNEL); + if (!policy) { + error = -ENOENT; + info = "parent does not exist"; + goto fail_lock; + } } - rcu_assign_pointer(ent->new->parent, aa_get_profile(p)); - } else if (policy != &ns->base) { - /* released on profile replacement or free_profile */ - struct aa_profile *p = (struct aa_profile *) policy; - rcu_assign_pointer(ent->new->parent, aa_get_profile(p)); } + if (!p && policy != &ns->base) + /* released on profile replacement or free_profile */ + p = (struct aa_profile *) policy; + rcu_assign_pointer(ent->new->parent, aa_get_profile(p)); } /* create new fs entries for introspection if needed */ @@ -1170,7 +1302,7 @@ ssize_t aa_remove_profiles(struct aa_ns *policy_ns, struct aa_label *subj, if (!name) { /* remove namespace - can only happen if fqname[0] == ':' */ - mutex_lock_nested(&ns->parent->lock, ns->level); + mutex_lock_nested(&ns->parent->lock, ns->parent->level); __aa_bump_ns_revision(ns); __aa_remove_ns(ns); mutex_unlock(&ns->parent->lock); diff --git a/security/apparmor/policy_compat.c b/security/apparmor/policy_compat.c new file mode 100644 index 000000000000..9e52e218bf30 --- /dev/null +++ b/security/apparmor/policy_compat.c @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AppArmor security module + * + * This file contains AppArmor functions for unpacking policy loaded + * from userspace. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2022 Canonical Ltd. + * + * Code to provide backwards compatibility with older policy versions, + * by converting/mapping older policy formats into the newer internal + * formats. + */ + +#include <linux/ctype.h> +#include <linux/errno.h> + +#include "include/lib.h" +#include "include/policy_unpack.h" +#include "include/policy_compat.h" + +/* remap old accept table embedded permissions to separate permission table */ +static u32 dfa_map_xindex(u16 mask) +{ + u16 old_index = (mask >> 10) & 0xf; + u32 index = 0; + + if (mask & 0x100) + index |= AA_X_UNSAFE; + if (mask & 0x200) + index |= AA_X_INHERIT; + if (mask & 0x80) + index |= AA_X_UNCONFINED; + + if (old_index == 1) { + index |= AA_X_UNCONFINED; + } else if (old_index == 2) { + index |= AA_X_NAME; + } else if (old_index == 3) { + index |= AA_X_NAME | AA_X_CHILD; + } else if (old_index) { + index |= AA_X_TABLE; + index |= old_index - 4; + } + + return index; +} + +/* + * map old dfa inline permissions to new format + */ +#define dfa_user_allow(dfa, state) (((ACCEPT_TABLE(dfa)[state]) & 0x7f) | \ + ((ACCEPT_TABLE(dfa)[state]) & 0x80000000)) +#define dfa_user_xbits(dfa, state) (((ACCEPT_TABLE(dfa)[state]) >> 7) & 0x7f) +#define dfa_user_audit(dfa, state) ((ACCEPT_TABLE2(dfa)[state]) & 0x7f) +#define dfa_user_quiet(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 7) & 0x7f) +#define dfa_user_xindex(dfa, state) \ + (dfa_map_xindex(ACCEPT_TABLE(dfa)[state] & 0x3fff)) + +#define dfa_other_allow(dfa, state) ((((ACCEPT_TABLE(dfa)[state]) >> 14) & \ + 0x7f) | \ + ((ACCEPT_TABLE(dfa)[state]) & 0x80000000)) +#define dfa_other_xbits(dfa, state) \ + ((((ACCEPT_TABLE(dfa)[state]) >> 7) >> 14) & 0x7f) +#define dfa_other_audit(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 14) & 0x7f) +#define dfa_other_quiet(dfa, state) \ + ((((ACCEPT_TABLE2(dfa)[state]) >> 7) >> 14) & 0x7f) +#define dfa_other_xindex(dfa, state) \ + dfa_map_xindex((ACCEPT_TABLE(dfa)[state] >> 14) & 0x3fff) + +/** + * map_old_perms - map old file perms layout to the new layout + * @old: permission set in old mapping + * + * Returns: new permission mapping + */ +static u32 map_old_perms(u32 old) +{ + u32 new = old & 0xf; + + if (old & MAY_READ) + new |= AA_MAY_GETATTR | AA_MAY_OPEN; + if (old & MAY_WRITE) + new |= AA_MAY_SETATTR | AA_MAY_CREATE | AA_MAY_DELETE | + AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_OPEN; + if (old & 0x10) + new |= AA_MAY_LINK; + /* the old mapping lock and link_subset flags where overlaid + * and use was determined by part of a pair that they were in + */ + if (old & 0x20) + new |= AA_MAY_LOCK | AA_LINK_SUBSET; + if (old & 0x40) /* AA_EXEC_MMAP */ + new |= AA_EXEC_MMAP; + + return new; +} + +static void compute_fperms_allow(struct aa_perms *perms, struct aa_dfa *dfa, + aa_state_t state) +{ + perms->allow |= AA_MAY_GETATTR; + + /* change_profile wasn't determined by ownership in old mapping */ + if (ACCEPT_TABLE(dfa)[state] & 0x80000000) + perms->allow |= AA_MAY_CHANGE_PROFILE; + if (ACCEPT_TABLE(dfa)[state] & 0x40000000) + perms->allow |= AA_MAY_ONEXEC; +} + +static struct aa_perms compute_fperms_user(struct aa_dfa *dfa, + aa_state_t state) +{ + struct aa_perms perms = { }; + + perms.allow = map_old_perms(dfa_user_allow(dfa, state)); + perms.audit = map_old_perms(dfa_user_audit(dfa, state)); + perms.quiet = map_old_perms(dfa_user_quiet(dfa, state)); + perms.xindex = dfa_user_xindex(dfa, state); + + compute_fperms_allow(&perms, dfa, state); + + return perms; +} + +static struct aa_perms compute_fperms_other(struct aa_dfa *dfa, + aa_state_t state) +{ + struct aa_perms perms = { }; + + perms.allow = map_old_perms(dfa_other_allow(dfa, state)); + perms.audit = map_old_perms(dfa_other_audit(dfa, state)); + perms.quiet = map_old_perms(dfa_other_quiet(dfa, state)); + perms.xindex = dfa_other_xindex(dfa, state); + + compute_fperms_allow(&perms, dfa, state); + + return perms; +} + +/** + * 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 + * + * Returns: remapped perm table + */ +static struct aa_perms *compute_fperms(struct aa_dfa *dfa) +{ + aa_state_t state; + unsigned int state_count; + struct aa_perms *table; + + AA_BUG(!dfa); + + state_count = dfa->tables[YYTD_ID_BASE]->td_lolen; + /* DFAs are restricted from having a state_count of less than 2 */ + table = kvcalloc(state_count * 2, sizeof(struct aa_perms), GFP_KERNEL); + if (!table) + return NULL; + + /* zero init so skip the trap state (state == 0) */ + for (state = 1; state < state_count; state++) { + table[state * 2] = compute_fperms_user(dfa, state); + table[state * 2 + 1] = compute_fperms_other(dfa, state); + } + + return table; +} + +static struct aa_perms *compute_xmatch_perms(struct aa_dfa *xmatch) +{ + struct aa_perms *perms; + int state; + int state_count; + + AA_BUG(!xmatch); + + state_count = xmatch->tables[YYTD_ID_BASE]->td_lolen; + /* DFAs are restricted from having a state_count of less than 2 */ + perms = kvcalloc(state_count, sizeof(struct aa_perms), GFP_KERNEL); + + /* zero init so skip the trap state (state == 0) */ + for (state = 1; state < state_count; state++) + perms[state].allow = dfa_user_allow(xmatch, state); + + return perms; +} + +static u32 map_other(u32 x) +{ + return ((x & 0x3) << 8) | /* SETATTR/GETATTR */ + ((x & 0x1c) << 18) | /* ACCEPT/BIND/LISTEN */ + ((x & 0x60) << 19); /* SETOPT/GETOPT */ +} + +static u32 map_xbits(u32 x) +{ + return ((x & 0x1) << 7) | + ((x & 0x7e) << 9); +} + +static struct aa_perms compute_perms_entry(struct aa_dfa *dfa, + aa_state_t state, + u32 version) +{ + struct aa_perms perms = { }; + + perms.allow = dfa_user_allow(dfa, state); + perms.audit = dfa_user_audit(dfa, state); + perms.quiet = dfa_user_quiet(dfa, state); + + /* + * This mapping is convulated due to history. + * v1-v4: only file perms, which are handled by compute_fperms + * v5: added policydb which dropped user conditional to gain new + * perm bits, but had to map around the xbits because the + * userspace compiler was still munging them. + * v9: adds using the xbits in policydb because the compiler now + * supports treating policydb permission bits different. + * Unfortunately there is no way to force auditing on the + * perms represented by the xbits + */ + perms.allow |= map_other(dfa_other_allow(dfa, state)); + if (VERSION_LE(version, v8)) + perms.allow |= AA_MAY_LOCK; + else + perms.allow |= map_xbits(dfa_user_xbits(dfa, state)); + + /* + * for v5-v9 perm mapping in the policydb, the other set is used + * to extend the general perm set + */ + perms.audit |= map_other(dfa_other_audit(dfa, state)); + perms.quiet |= map_other(dfa_other_quiet(dfa, state)); + if (VERSION_GT(version, v8)) + perms.quiet |= map_xbits(dfa_other_xbits(dfa, state)); + + return perms; +} + +static struct aa_perms *compute_perms(struct aa_dfa *dfa, u32 version) +{ + unsigned int state; + unsigned int state_count; + struct aa_perms *table; + + AA_BUG(!dfa); + + state_count = dfa->tables[YYTD_ID_BASE]->td_lolen; + /* DFAs are restricted from having a state_count of less than 2 */ + table = kvcalloc(state_count, sizeof(struct aa_perms), GFP_KERNEL); + if (!table) + return NULL; + + /* zero init so skip the trap state (state == 0) */ + for (state = 1; state < state_count; state++) + table[state] = compute_perms_entry(dfa, state, version); + + return table; +} + +/** + * remap_dfa_accept - remap old dfa accept table to be an index + * @dfa: dfa to do the remapping on + * @factor: scaling factor for the index conversion. + * + * Used in conjunction with compute_Xperms, it converts old style perms + * that are encoded in the dfa accept tables to the new style where + * there is a permission table and the accept table is an index into + * the permission table. + */ +static void remap_dfa_accept(struct aa_dfa *dfa, unsigned int factor) +{ + unsigned int state; + unsigned int state_count = dfa->tables[YYTD_ID_BASE]->td_lolen; + + AA_BUG(!dfa); + + for (state = 0; state < state_count; state++) + ACCEPT_TABLE(dfa)[state] = state * factor; + kvfree(dfa->tables[YYTD_ID_ACCEPT2]); + dfa->tables[YYTD_ID_ACCEPT2] = NULL; +} + +/* TODO: merge different dfa mappings into single map_policy fn */ +int aa_compat_map_xmatch(struct aa_policydb *policy) +{ + policy->perms = compute_xmatch_perms(policy->dfa); + if (!policy->perms) + return -ENOMEM; + + remap_dfa_accept(policy->dfa, 1); + + return 0; +} + +int aa_compat_map_policy(struct aa_policydb *policy, u32 version) +{ + policy->perms = compute_perms(policy->dfa, version); + if (!policy->perms) + return -ENOMEM; + + remap_dfa_accept(policy->dfa, 1); + + return 0; +} + +int aa_compat_map_file(struct aa_policydb *policy) +{ + policy->perms = compute_fperms(policy->dfa); + if (!policy->perms) + return -ENOMEM; + + remap_dfa_accept(policy->dfa, 2); + + return 0; +} diff --git a/security/apparmor/policy_ns.c b/security/apparmor/policy_ns.c index 43beaad083fe..fd5b7afbcb48 100644 --- a/security/apparmor/policy_ns.c +++ b/security/apparmor/policy_ns.c @@ -84,15 +84,13 @@ static struct aa_profile *alloc_unconfined(const char *name) { struct aa_profile *profile; - profile = aa_alloc_profile(name, NULL, GFP_KERNEL); + profile = aa_alloc_null(NULL, name, GFP_KERNEL); if (!profile) return NULL; profile->label.flags |= FLAG_IX_ON_NAME_ERROR | FLAG_IMMUTIBLE | FLAG_NS_COUNT | FLAG_UNCONFINED; profile->mode = APPARMOR_UNCONFINED; - profile->file.dfa = aa_get_dfa(nulldfa); - profile->policy.dfa = aa_get_dfa(nulldfa); return profile; } @@ -134,7 +132,7 @@ static struct aa_ns *alloc_ns(const char *prefix, const char *name) return ns; fail_unconfined: - kfree_sensitive(ns->base.hname); + aa_policy_destroy(&ns->base); fail_ns: kfree_sensitive(ns); return NULL; diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 12e535fdfa8b..66915653108c 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -17,26 +17,18 @@ #include <kunit/visibility.h> #include <linux/ctype.h> #include <linux/errno.h> -#include <linux/zlib.h> +#include <linux/zstd.h> #include "include/apparmor.h" #include "include/audit.h" #include "include/cred.h" #include "include/crypto.h" +#include "include/file.h" #include "include/match.h" #include "include/path.h" #include "include/policy.h" #include "include/policy_unpack.h" - -#define K_ABI_MASK 0x3ff -#define FORCE_COMPLAIN_FLAG 0x800 -#define VERSION_LT(X, Y) (((X) & K_ABI_MASK) < ((Y) & K_ABI_MASK)) -#define VERSION_GT(X, Y) (((X) & K_ABI_MASK) > ((Y) & K_ABI_MASK)) - -#define v5 5 /* base version */ -#define v6 6 /* per entry policydb mediation check */ -#define v7 7 -#define v8 8 /* full network masking */ +#include "include/policy_compat.h" /* audit callback for unpack fields */ static void audit_cb(struct audit_buffer *ab, void *va) @@ -71,7 +63,7 @@ static int audit_iface(struct aa_profile *new, const char *ns_name, int error) { struct aa_profile *profile = labels_profile(aa_current_raw_label()); - DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, NULL); + DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, AA_CLASS_NONE, NULL); if (e) aad(&sa)->iface.pos = e->pos - e->start; aad(&sa)->iface.ns = ns_name; @@ -321,22 +313,21 @@ fail: } EXPORT_SYMBOL_IF_KUNIT(aa_unpack_u64); -VISIBLE_IF_KUNIT size_t aa_unpack_array(struct aa_ext *e, const char *name) +VISIBLE_IF_KUNIT bool aa_unpack_array(struct aa_ext *e, const char *name, u16 *size) { void *pos = e->pos; if (aa_unpack_nameX(e, AA_ARRAY, name)) { - int size; if (!aa_inbounds(e, sizeof(u16))) goto fail; - size = (int)le16_to_cpu(get_unaligned((__le16 *) e->pos)); + *size = le16_to_cpu(get_unaligned((__le16 *) e->pos)); e->pos += sizeof(u16); - return size; + return true; } fail: e->pos = pos; - return 0; + return false; } EXPORT_SYMBOL_IF_KUNIT(aa_unpack_array); @@ -411,10 +402,11 @@ EXPORT_SYMBOL_IF_KUNIT(aa_unpack_strdup); /** * unpack_dfa - unpack a file rule dfa * @e: serialized data extent information (NOT NULL) + * @flags: dfa flags to check * * returns dfa or ERR_PTR or NULL if no dfa */ -static struct aa_dfa *unpack_dfa(struct aa_ext *e) +static struct aa_dfa *unpack_dfa(struct aa_ext *e, int flags) { char *blob = NULL; size_t size; @@ -430,8 +422,6 @@ static struct aa_dfa *unpack_dfa(struct aa_ext *e) size_t sz = blob - (char *) e->start - ((e->pos - e->start) & 7); size_t pad = ALIGN(sz, 8) - sz; - int flags = TO_ACCEPT1_FLAG(YYTD_DATA32) | - TO_ACCEPT2_FLAG(YYTD_DATA32); if (aa_g_paranoid_load) flags |= DFA_FLAG_VERIFY_STATES; dfa = aa_dfa_unpack(blob + pad, size - pad, flags); @@ -447,28 +437,32 @@ static struct aa_dfa *unpack_dfa(struct aa_ext *e) /** * unpack_trans_table - unpack a profile transition table * @e: serialized data extent information (NOT NULL) - * @profile: profile to add the accept table to (NOT NULL) + * @table: str table to unpack to (NOT NULL) * - * Returns: true if table successfully unpacked + * Returns: true if table successfully unpacked or not present */ -static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile) +static bool unpack_trans_table(struct aa_ext *e, struct aa_str_table *strs) { void *saved_pos = e->pos; + char **table = NULL; /* exec table is optional */ if (aa_unpack_nameX(e, AA_STRUCT, "xtable")) { - int i, size; - - size = aa_unpack_array(e, NULL); - /* currently 4 exec bits and entries 0-3 are reserved iupcx */ - if (size > 16 - 4) + u16 size; + int i; + + if (!aa_unpack_array(e, NULL, &size)) + /* + * Note: index into trans table array is a max + * of 2^24, but unpack array can only unpack + * an array of 2^16 in size atm so no need + * for size check here + */ goto fail; - profile->file.trans.table = kcalloc(size, sizeof(char *), - GFP_KERNEL); - if (!profile->file.trans.table) + table = kcalloc(size, sizeof(char *), GFP_KERNEL); + if (!table) goto fail; - profile->file.trans.size = size; for (i = 0; i < size; i++) { char *str; int c, j, pos, size2 = aa_unpack_strdup(e, &str, NULL); @@ -477,7 +471,7 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile) */ if (!size2) goto fail; - profile->file.trans.table[i] = str; + table[i] = str; /* verify that name doesn't start with space */ if (isspace(*str)) goto fail; @@ -511,11 +505,14 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile) goto fail; if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) goto fail; + + strs->table = table; + strs->size = size; } return true; fail: - aa_free_domain_entries(&profile->file.trans); + kfree_sensitive(table); e->pos = saved_pos; return false; } @@ -525,15 +522,17 @@ static bool unpack_xattrs(struct aa_ext *e, struct aa_profile *profile) void *pos = e->pos; if (aa_unpack_nameX(e, AA_STRUCT, "xattrs")) { - int i, size; + u16 size; + int i; - size = aa_unpack_array(e, NULL); - profile->xattr_count = size; - profile->xattrs = kcalloc(size, sizeof(char *), GFP_KERNEL); - if (!profile->xattrs) + if (!aa_unpack_array(e, NULL, &size)) + goto fail; + profile->attach.xattr_count = size; + profile->attach.xattrs = kcalloc(size, sizeof(char *), GFP_KERNEL); + if (!profile->attach.xattrs) goto fail; for (i = 0; i < size; i++) { - if (!aa_unpack_strdup(e, &profile->xattrs[i], NULL)) + if (!aa_unpack_strdup(e, &profile->attach.xattrs[i], NULL)) goto fail; } if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL)) @@ -549,27 +548,29 @@ fail: return false; } -static bool unpack_secmark(struct aa_ext *e, struct aa_profile *profile) +static bool unpack_secmark(struct aa_ext *e, struct aa_ruleset *rules) { void *pos = e->pos; - int i, size; + u16 size; + int i; if (aa_unpack_nameX(e, AA_STRUCT, "secmark")) { - size = aa_unpack_array(e, NULL); + if (!aa_unpack_array(e, NULL, &size)) + goto fail; - profile->secmark = kcalloc(size, sizeof(struct aa_secmark), + rules->secmark = kcalloc(size, sizeof(struct aa_secmark), GFP_KERNEL); - if (!profile->secmark) + if (!rules->secmark) goto fail; - profile->secmark_count = size; + rules->secmark_count = size; for (i = 0; i < size; i++) { - if (!unpack_u8(e, &profile->secmark[i].audit, NULL)) + if (!unpack_u8(e, &rules->secmark[i].audit, NULL)) goto fail; - if (!unpack_u8(e, &profile->secmark[i].deny, NULL)) + if (!unpack_u8(e, &rules->secmark[i].deny, NULL)) goto fail; - if (!aa_unpack_strdup(e, &profile->secmark[i].label, NULL)) + if (!aa_unpack_strdup(e, &rules->secmark[i].label, NULL)) goto fail; } if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL)) @@ -581,39 +582,40 @@ static bool unpack_secmark(struct aa_ext *e, struct aa_profile *profile) return true; fail: - if (profile->secmark) { + if (rules->secmark) { for (i = 0; i < size; i++) - kfree(profile->secmark[i].label); - kfree(profile->secmark); - profile->secmark_count = 0; - profile->secmark = NULL; + kfree(rules->secmark[i].label); + kfree(rules->secmark); + rules->secmark_count = 0; + rules->secmark = NULL; } e->pos = pos; return false; } -static bool unpack_rlimits(struct aa_ext *e, struct aa_profile *profile) +static bool unpack_rlimits(struct aa_ext *e, struct aa_ruleset *rules) { void *pos = e->pos; /* rlimits are optional */ if (aa_unpack_nameX(e, AA_STRUCT, "rlimits")) { - int i, size; + u16 size; + int i; u32 tmp = 0; if (!aa_unpack_u32(e, &tmp, NULL)) goto fail; - profile->rlimits.mask = tmp; + rules->rlimits.mask = tmp; - size = aa_unpack_array(e, NULL); - if (size > RLIM_NLIMITS) + if (!aa_unpack_array(e, NULL, &size) || + size > RLIM_NLIMITS) goto fail; for (i = 0; i < size; i++) { u64 tmp2 = 0; int a = aa_map_resource(i); if (!aa_unpack_u64(e, &tmp2, NULL)) goto fail; - profile->rlimits.limits[a].rlim_max = tmp2; + rules->rlimits.limits[a].rlim_max = tmp2; } if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL)) goto fail; @@ -627,6 +629,140 @@ fail: return false; } +static bool unpack_perm(struct aa_ext *e, u32 version, struct aa_perms *perm) +{ + if (version != 1) + return false; + + return aa_unpack_u32(e, &perm->allow, NULL) && + aa_unpack_u32(e, &perm->allow, NULL) && + aa_unpack_u32(e, &perm->deny, NULL) && + aa_unpack_u32(e, &perm->subtree, NULL) && + aa_unpack_u32(e, &perm->cond, NULL) && + aa_unpack_u32(e, &perm->kill, NULL) && + aa_unpack_u32(e, &perm->complain, NULL) && + aa_unpack_u32(e, &perm->prompt, NULL) && + aa_unpack_u32(e, &perm->audit, NULL) && + aa_unpack_u32(e, &perm->quiet, NULL) && + aa_unpack_u32(e, &perm->hide, NULL) && + aa_unpack_u32(e, &perm->xindex, NULL) && + aa_unpack_u32(e, &perm->tag, NULL) && + aa_unpack_u32(e, &perm->label, NULL); +} + +static ssize_t unpack_perms_table(struct aa_ext *e, struct aa_perms **perms) +{ + void *pos = e->pos; + u16 size = 0; + + AA_BUG(!perms); + /* + * policy perms are optional, in which case perms are embedded + * in the dfa accept table + */ + if (aa_unpack_nameX(e, AA_STRUCT, "perms")) { + int i; + u32 version; + + if (!aa_unpack_u32(e, &version, "version")) + goto fail_reset; + if (!aa_unpack_array(e, NULL, &size)) + goto fail_reset; + *perms = kcalloc(size, sizeof(struct aa_perms), GFP_KERNEL); + if (!*perms) + goto fail_reset; + for (i = 0; i < size; i++) { + if (!unpack_perm(e, version, &(*perms)[i])) + goto fail; + } + if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) + goto fail; + } else + *perms = NULL; + + return size; + +fail: + kfree(*perms); +fail_reset: + e->pos = pos; + return -EPROTO; +} + +static int unpack_pdb(struct aa_ext *e, struct aa_policydb *policy, + bool required_dfa, bool required_trans, + const char **info) +{ + void *pos = e->pos; + int i, flags, error = -EPROTO; + ssize_t size; + + size = unpack_perms_table(e, &policy->perms); + if (size < 0) { + error = size; + policy->perms = NULL; + *info = "failed to unpack - perms"; + goto fail; + } + policy->size = size; + + if (policy->perms) { + /* perms table present accept is index */ + flags = TO_ACCEPT1_FLAG(YYTD_DATA32); + } else { + /* packed perms in accept1 and accept2 */ + flags = TO_ACCEPT1_FLAG(YYTD_DATA32) | + TO_ACCEPT2_FLAG(YYTD_DATA32); + } + + policy->dfa = unpack_dfa(e, flags); + if (IS_ERR(policy->dfa)) { + error = PTR_ERR(policy->dfa); + policy->dfa = NULL; + *info = "failed to unpack - dfa"; + goto fail; + } else if (!policy->dfa) { + if (required_dfa) { + *info = "missing required dfa"; + goto fail; + } + goto out; + } + + /* + * only unpack the following if a dfa is present + * + * 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")) + /* default start state */ + policy->start[0] = DFA_START; + if (!aa_unpack_u32(e, &policy->start[AA_CLASS_FILE], "dfa_start")) { + /* default start state for xmatch and file dfa */ + policy->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], + i); + } + if (!unpack_trans_table(e, &policy->trans) && required_trans) { + *info = "failed to unpack profile transition table"; + goto fail; + } + + /* TODO: move compat mapping here, requires dfa merging first */ + /* TODO: move verify here, it has to be done after compat mappings */ +out: + return 0; + +fail: + e->pos = pos; + return error; +} + static u32 strhash(const void *data, u32 len, u32 seed) { const char * const *key = data; @@ -651,6 +787,7 @@ static int datacmp(struct rhashtable_compare_arg *arg, const void *obj) */ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) { + struct aa_ruleset *rules; struct aa_profile *profile = NULL; const char *tmpname, *tmpns = NULL, *name = NULL; const char *info = "failed to unpack profile"; @@ -658,7 +795,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) struct rhashtable_params params = { 0 }; char *key = NULL; struct aa_data *data; - int i, error = -EPROTO; + int error = -EPROTO; kernel_cap_t tmpcap; u32 tmp; @@ -677,36 +814,46 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) *ns_name = kstrndup(tmpns, ns_len, GFP_KERNEL); if (!*ns_name) { info = "out of memory"; + error = -ENOMEM; goto fail; } name = tmpname; } profile = aa_alloc_profile(name, NULL, GFP_KERNEL); - if (!profile) - return ERR_PTR(-ENOMEM); + if (!profile) { + info = "out of memory"; + error = -ENOMEM; + goto fail; + } + rules = list_first_entry(&profile->rules, typeof(*rules), list); /* profile renaming is optional */ (void) aa_unpack_str(e, &profile->rename, "rename"); /* attachment string is optional */ - (void) aa_unpack_str(e, &profile->attach, "attach"); + (void) aa_unpack_str(e, &profile->attach.xmatch_str, "attach"); /* xmatch is optional and may be NULL */ - profile->xmatch = unpack_dfa(e); - if (IS_ERR(profile->xmatch)) { - error = PTR_ERR(profile->xmatch); - profile->xmatch = NULL; + error = unpack_pdb(e, &profile->attach.xmatch, false, false, &info); + if (error) { info = "bad xmatch"; goto fail; } - /* xmatch_len is not optional if xmatch is set */ - if (profile->xmatch) { + + /* neither xmatch_len not xmatch_perms are optional if xmatch is set */ + if (profile->attach.xmatch.dfa) { if (!aa_unpack_u32(e, &tmp, NULL)) { info = "missing xmatch len"; goto fail; } - profile->xmatch_len = tmp; + profile->attach.xmatch_len = tmp; + profile->attach.xmatch.start[AA_CLASS_XMATCH] = DFA_START; + error = aa_compat_map_xmatch(&profile->attach.xmatch); + if (error) { + info = "failed to convert xmatch permission table"; + goto fail; + } } /* disconnected attachment string is optional */ @@ -737,6 +884,8 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) } else if (tmp == PACKED_MODE_UNCONFINED) { profile->mode = APPARMOR_UNCONFINED; profile->label.flags |= FLAG_UNCONFINED; + } else if (tmp == PACKED_MODE_USER) { + profile->mode = APPARMOR_USER; } else { goto fail; } @@ -757,11 +906,11 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) profile->path_flags = PATH_MEDIATE_DELETED; info = "failed to unpack profile capabilities"; - if (!aa_unpack_u32(e, &(profile->caps.allow.cap[0]), NULL)) + if (!aa_unpack_u32(e, &(rules->caps.allow.cap[0]), NULL)) goto fail; - if (!aa_unpack_u32(e, &(profile->caps.audit.cap[0]), NULL)) + if (!aa_unpack_u32(e, &(rules->caps.audit.cap[0]), NULL)) goto fail; - if (!aa_unpack_u32(e, &(profile->caps.quiet.cap[0]), NULL)) + if (!aa_unpack_u32(e, &(rules->caps.quiet.cap[0]), NULL)) goto fail; if (!aa_unpack_u32(e, &tmpcap.cap[0], NULL)) goto fail; @@ -769,11 +918,11 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) info = "failed to unpack upper profile capabilities"; if (aa_unpack_nameX(e, AA_STRUCT, "caps64")) { /* optional upper half of 64 bit caps */ - if (!aa_unpack_u32(e, &(profile->caps.allow.cap[1]), NULL)) + if (!aa_unpack_u32(e, &(rules->caps.allow.cap[1]), NULL)) goto fail; - if (!aa_unpack_u32(e, &(profile->caps.audit.cap[1]), NULL)) + if (!aa_unpack_u32(e, &(rules->caps.audit.cap[1]), NULL)) goto fail; - if (!aa_unpack_u32(e, &(profile->caps.quiet.cap[1]), NULL)) + if (!aa_unpack_u32(e, &(rules->caps.quiet.cap[1]), NULL)) goto fail; if (!aa_unpack_u32(e, &(tmpcap.cap[1]), NULL)) goto fail; @@ -784,9 +933,9 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) info = "failed to unpack extended profile capabilities"; if (aa_unpack_nameX(e, AA_STRUCT, "capsx")) { /* optional extended caps mediation mask */ - if (!aa_unpack_u32(e, &(profile->caps.extended.cap[0]), NULL)) + if (!aa_unpack_u32(e, &(rules->caps.extended.cap[0]), NULL)) goto fail; - if (!aa_unpack_u32(e, &(profile->caps.extended.cap[1]), NULL)) + if (!aa_unpack_u32(e, &(rules->caps.extended.cap[1]), NULL)) goto fail; if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) goto fail; @@ -797,12 +946,12 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) goto fail; } - if (!unpack_rlimits(e, profile)) { + if (!unpack_rlimits(e, rules)) { info = "failed to unpack profile rlimits"; goto fail; } - if (!unpack_secmark(e, profile)) { + if (!unpack_secmark(e, rules)) { info = "failed to unpack profile secmark rules"; goto fail; } @@ -810,59 +959,52 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) if (aa_unpack_nameX(e, AA_STRUCT, "policydb")) { /* generic policy dfa - optional and may be NULL */ info = "failed to unpack policydb"; - profile->policy.dfa = unpack_dfa(e); - if (IS_ERR(profile->policy.dfa)) { - error = PTR_ERR(profile->policy.dfa); - profile->policy.dfa = NULL; - goto fail; - } else if (!profile->policy.dfa) { - error = -EPROTO; + error = unpack_pdb(e, &rules->policy, true, false, + &info); + if (error) goto fail; - } - if (!aa_unpack_u32(e, &profile->policy.start[0], "start")) - /* default start state */ - profile->policy.start[0] = DFA_START; - /* setup class index */ - for (i = AA_CLASS_FILE; i <= AA_CLASS_LAST; i++) { - profile->policy.start[i] = - aa_dfa_next(profile->policy.dfa, - profile->policy.start[0], - i); - } + /* Fixup: drop when we get rid of start array */ + 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], + AA_CLASS_FILE); if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) goto fail; + error = aa_compat_map_policy(&rules->policy, e->version); + if (error) { + info = "failed to remap policydb permission table"; + goto fail; + } } else - profile->policy.dfa = aa_get_dfa(nulldfa); + rules->policy.dfa = aa_get_dfa(nulldfa); /* get file rules */ - profile->file.dfa = unpack_dfa(e); - if (IS_ERR(profile->file.dfa)) { - error = PTR_ERR(profile->file.dfa); - profile->file.dfa = NULL; - info = "failed to unpack profile file rules"; + error = unpack_pdb(e, &rules->file, false, true, &info); + if (error) { goto fail; - } else if (profile->file.dfa) { - if (!aa_unpack_u32(e, &profile->file.start, "dfa_start")) - /* default start state */ - profile->file.start = DFA_START; - } else if (profile->policy.dfa && - profile->policy.start[AA_CLASS_FILE]) { - profile->file.dfa = aa_get_dfa(profile->policy.dfa); - profile->file.start = profile->policy.start[AA_CLASS_FILE]; + } else if (rules->file.dfa) { + 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]; } else - profile->file.dfa = aa_get_dfa(nulldfa); - - if (!unpack_trans_table(e, profile)) { - info = "failed to unpack profile transition table"; - goto fail; - } + rules->file.dfa = aa_get_dfa(nulldfa); + error = -EPROTO; if (aa_unpack_nameX(e, AA_STRUCT, "data")) { info = "out of memory"; profile->data = kzalloc(sizeof(*profile->data), GFP_KERNEL); - if (!profile->data) + if (!profile->data) { + error = -ENOMEM; goto fail; - + } params.nelem_hint = 3; params.key_len = sizeof(void *); params.key_offset = offsetof(struct aa_data, key); @@ -879,6 +1021,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) data = kzalloc(sizeof(*data), GFP_KERNEL); if (!data) { kfree_sensitive(key); + error = -ENOMEM; goto fail; } @@ -888,6 +1031,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) if (data->size && !data->data) { kfree_sensitive(data->key); kfree_sensitive(data); + error = -ENOMEM; goto fail; } @@ -909,6 +1053,13 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) return profile; fail: + if (error == 0) + /* default error covers most cases */ + error = -EPROTO; + if (*ns_name) { + kfree(*ns_name); + *ns_name = NULL; + } if (profile) name = NULL; else if (!name) @@ -946,7 +1097,7 @@ static int verify_header(struct aa_ext *e, int required, const char **ns) * if not specified use previous version * Mask off everything that is not kernel abi version */ - if (VERSION_LT(e->version, v5) || VERSION_GT(e->version, v7)) { + if (VERSION_LT(e->version, v5) || VERSION_GT(e->version, v9)) { audit_iface(NULL, NULL, NULL, "unsupported interface version", e, error); return error; @@ -987,11 +1138,51 @@ static bool verify_dfa_xindex(struct aa_dfa *dfa, int table_size) { int i; for (i = 0; i < dfa->tables[YYTD_ID_ACCEPT]->td_lolen; i++) { - if (!verify_xindex(dfa_user_xindex(dfa, i), table_size)) + if (!verify_xindex(ACCEPT_TABLE(dfa)[i], table_size)) + return false; + } + return true; +} + +static bool verify_perm(struct aa_perms *perm) +{ + /* TODO: allow option to just force the perms into a valid state */ + if (perm->allow & perm->deny) + return false; + if (perm->subtree & ~perm->allow) + return false; + if (perm->cond & (perm->allow | perm->deny)) + return false; + if (perm->kill & perm->allow) + return false; + if (perm->complain & (perm->allow | perm->deny)) + return false; + if (perm->prompt & (perm->allow | perm->deny)) + return false; + if (perm->complain & perm->prompt) + return false; + if (perm->hide & perm->allow) + return false; + + return true; +} + +static bool verify_perms(struct aa_policydb *pdb) +{ + int i; + + for (i = 0; i < pdb->size; i++) { + if (!verify_perm(&pdb->perms[i])) + return false; + /* verify indexes into str table */ + if (pdb->perms[i].xindex >= pdb->trans.size) return false; - if (!verify_xindex(dfa_other_xindex(dfa, i), table_size)) + if (pdb->perms[i].tag >= pdb->trans.size) + return false; + if (pdb->perms[i].label >= pdb->trans.size) return false; } + return true; } @@ -1000,14 +1191,38 @@ static bool verify_dfa_xindex(struct aa_dfa *dfa, int table_size) * @profile: profile to verify (NOT NULL) * * Returns: 0 if passes verification else error + * + * This verification is post any unpack mapping or changes */ static int verify_profile(struct aa_profile *profile) { - if (profile->file.dfa && - !verify_dfa_xindex(profile->file.dfa, - profile->file.trans.size)) { - audit_iface(profile, NULL, NULL, "Invalid named transition", - NULL, -EPROTO); + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); + if (!rules) + return 0; + + if ((rules->file.dfa && !verify_dfa_xindex(rules->file.dfa, + rules->file.trans.size)) || + (rules->policy.dfa && + !verify_dfa_xindex(rules->policy.dfa, rules->policy.trans.size))) { + audit_iface(profile, NULL, NULL, + "Unpack: Invalid named transition", NULL, -EPROTO); + return -EPROTO; + } + + if (!verify_perms(&rules->file)) { + audit_iface(profile, NULL, NULL, + "Unpack: Invalid perm index", NULL, -EPROTO); + return -EPROTO; + } + if (!verify_perms(&rules->policy)) { + audit_iface(profile, NULL, NULL, + "Unpack: Invalid perm index", NULL, -EPROTO); + return -EPROTO; + } + if (!verify_perms(&profile->attach.xmatch)) { + audit_iface(profile, NULL, NULL, + "Unpack: Invalid perm index", NULL, -EPROTO); return -EPROTO; } @@ -1033,81 +1248,73 @@ struct aa_load_ent *aa_load_ent_alloc(void) return ent; } -static int deflate_compress(const char *src, size_t slen, char **dst, - size_t *dlen) +static int compress_zstd(const char *src, size_t slen, char **dst, size_t *dlen) { #ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY - int error; - struct z_stream_s strm; - void *stgbuf, *dstbuf; - size_t stglen = deflateBound(slen); - - memset(&strm, 0, sizeof(strm)); - - if (stglen < slen) - return -EFBIG; - - strm.workspace = kvzalloc(zlib_deflate_workspacesize(MAX_WBITS, - MAX_MEM_LEVEL), - GFP_KERNEL); - if (!strm.workspace) - return -ENOMEM; - - error = zlib_deflateInit(&strm, aa_g_rawdata_compression_level); - if (error != Z_OK) { - error = -ENOMEM; - goto fail_deflate_init; + const zstd_parameters params = + zstd_get_params(aa_g_rawdata_compression_level, slen); + const size_t wksp_len = zstd_cctx_workspace_bound(¶ms.cParams); + void *wksp = NULL; + zstd_cctx *ctx = NULL; + size_t out_len = zstd_compress_bound(slen); + void *out = NULL; + int ret = 0; + + out = kvzalloc(out_len, GFP_KERNEL); + if (!out) { + ret = -ENOMEM; + goto cleanup; } - stgbuf = kvzalloc(stglen, GFP_KERNEL); - if (!stgbuf) { - error = -ENOMEM; - goto fail_stg_alloc; + wksp = kvzalloc(wksp_len, GFP_KERNEL); + if (!wksp) { + ret = -ENOMEM; + goto cleanup; } - strm.next_in = src; - strm.avail_in = slen; - strm.next_out = stgbuf; - strm.avail_out = stglen; + ctx = zstd_init_cctx(wksp, wksp_len); + if (!ctx) { + ret = -EINVAL; + goto cleanup; + } - error = zlib_deflate(&strm, Z_FINISH); - if (error != Z_STREAM_END) { - error = -EINVAL; - goto fail_deflate; + out_len = zstd_compress_cctx(ctx, out, out_len, src, slen, ¶ms); + if (zstd_is_error(out_len) || out_len >= slen) { + ret = -EINVAL; + goto cleanup; } - error = 0; - if (is_vmalloc_addr(stgbuf)) { - dstbuf = kvzalloc(strm.total_out, GFP_KERNEL); - if (dstbuf) { - memcpy(dstbuf, stgbuf, strm.total_out); - kvfree(stgbuf); + if (is_vmalloc_addr(out)) { + *dst = kvzalloc(out_len, GFP_KERNEL); + if (*dst) { + memcpy(*dst, out, out_len); + kvfree(out); + out = NULL; } - } else + } else { /* * If the staging buffer was kmalloc'd, then using krealloc is * probably going to be faster. The destination buffer will * always be smaller, so it's just shrunk, avoiding a memcpy */ - dstbuf = krealloc(stgbuf, strm.total_out, GFP_KERNEL); + *dst = krealloc(out, out_len, GFP_KERNEL); + } - if (!dstbuf) { - error = -ENOMEM; - goto fail_deflate; + if (!*dst) { + ret = -ENOMEM; + goto cleanup; } - *dst = dstbuf; - *dlen = strm.total_out; + *dlen = out_len; -fail_stg_alloc: - zlib_deflateEnd(&strm); -fail_deflate_init: - kvfree(strm.workspace); - return error; +cleanup: + if (ret) { + kvfree(out); + *dst = NULL; + } -fail_deflate: - kvfree(stgbuf); - goto fail_stg_alloc; + kvfree(wksp); + return ret; #else *dlen = slen; return 0; @@ -1116,7 +1323,6 @@ fail_deflate: static int compress_loaddata(struct aa_loaddata *data) { - AA_BUG(data->compressed_size > 0); /* @@ -1125,11 +1331,12 @@ static int compress_loaddata(struct aa_loaddata *data) */ if (aa_g_rawdata_compression_level != 0) { void *udata = data->data; - int error = deflate_compress(udata, data->size, &data->data, - &data->compressed_size); - if (error) + int error = compress_zstd(udata, data->size, &data->data, + &data->compressed_size); + if (error) { + data->compressed_size = data->size; return error; - + } if (udata != data->data) kvfree(udata); } else @@ -1155,6 +1362,7 @@ int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, { struct aa_load_ent *tmp, *ent; struct aa_profile *profile = NULL; + char *ns_name = NULL; int error; struct aa_ext e = { .start = udata->data, @@ -1164,7 +1372,6 @@ int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, *ns = NULL; while (e.pos < e.end) { - char *ns_name = NULL; void *start; error = verify_header(&e, e.pos == e.start, ns); if (error) @@ -1195,6 +1402,7 @@ int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, ent->new = profile; ent->ns_name = ns_name; + ns_name = NULL; list_add_tail(&ent->list, lh); } udata->abi = e.version & K_ABI_MASK; @@ -1215,6 +1423,7 @@ int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, return 0; fail_profile: + kfree(ns_name); aa_put_profile(profile); fail: diff --git a/security/apparmor/policy_unpack_test.c b/security/apparmor/policy_unpack_test.c index f25cf2a023d5..e1bfdab524b7 100644 --- a/security/apparmor/policy_unpack_test.c +++ b/security/apparmor/policy_unpack_test.c @@ -143,12 +143,11 @@ static void policy_unpack_test_inbounds_when_out_of_bounds(struct kunit *test) static void policy_unpack_test_unpack_array_with_null_name(struct kunit *test) { struct policy_unpack_fixture *puf = test->priv; - u16 array_size; + u16 array_size = 0; puf->e->pos += TEST_ARRAY_BUF_OFFSET; - array_size = aa_unpack_array(puf->e, NULL); - + KUNIT_EXPECT_TRUE(test, aa_unpack_array(puf->e, NULL, &array_size)); KUNIT_EXPECT_EQ(test, array_size, (u16)TEST_ARRAY_SIZE); KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, puf->e->start + TEST_ARRAY_BUF_OFFSET + sizeof(u16) + 1); @@ -158,12 +157,11 @@ static void policy_unpack_test_unpack_array_with_name(struct kunit *test) { struct policy_unpack_fixture *puf = test->priv; const char name[] = TEST_ARRAY_NAME; - u16 array_size; + u16 array_size = 0; puf->e->pos += TEST_NAMED_ARRAY_BUF_OFFSET; - array_size = aa_unpack_array(puf->e, name); - + KUNIT_EXPECT_TRUE(test, aa_unpack_array(puf->e, name, &array_size)); KUNIT_EXPECT_EQ(test, array_size, (u16)TEST_ARRAY_SIZE); KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, puf->e->start + TEST_ARRAY_BUF_OFFSET + sizeof(u16) + 1); @@ -178,9 +176,7 @@ static void policy_unpack_test_unpack_array_out_of_bounds(struct kunit *test) puf->e->pos += TEST_NAMED_ARRAY_BUF_OFFSET; puf->e->end = puf->e->start + TEST_ARRAY_BUF_OFFSET + sizeof(u16); - array_size = aa_unpack_array(puf->e, name); - - KUNIT_EXPECT_EQ(test, array_size, 0); + KUNIT_EXPECT_FALSE(test, aa_unpack_array(puf->e, name, &array_size)); KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, puf->e->start + TEST_NAMED_ARRAY_BUF_OFFSET); } diff --git a/security/apparmor/procattr.c b/security/apparmor/procattr.c index 86ad26ef72ed..197d41f9c32b 100644 --- a/security/apparmor/procattr.c +++ b/security/apparmor/procattr.c @@ -17,14 +17,13 @@ /** - * aa_getprocattr - Return the profile information for @profile - * @profile: the profile to print profile info about (NOT NULL) - * @string: Returns - string containing the profile info (NOT NULL) + * aa_getprocattr - Return the label information for @label + * @label: the label to print label info about (NOT NULL) + * @string: Returns - string containing the label info (NOT NULL) * - * Requires: profile != NULL + * Requires: label != NULL && string != NULL * - * Creates a string containing the namespace_name://profile_name for - * @profile. + * Creates a string containing the label information for @label. * * Returns: size of string placed in @string else error code on failure */ diff --git a/security/apparmor/resource.c b/security/apparmor/resource.c index 1ae4874251a9..e85948164896 100644 --- a/security/apparmor/resource.c +++ b/security/apparmor/resource.c @@ -45,6 +45,8 @@ static void audit_cb(struct audit_buffer *ab, void *va) * @profile: profile being enforced (NOT NULL) * @resource: rlimit being auditing * @value: value being set + * @peer: aa_albel of the task being set + * @info: info being auditing * @error: error value * * Returns: 0 or sa->error else other error code on failure @@ -53,7 +55,8 @@ static int audit_resource(struct aa_profile *profile, unsigned int resource, unsigned long value, struct aa_label *peer, const char *info, int error) { - DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_SETRLIMIT); + DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, AA_CLASS_RLIMITS, + OP_SETRLIMIT); aad(&sa)->rlim.rlim = resource; aad(&sa)->rlim.max = value; @@ -65,7 +68,7 @@ static int audit_resource(struct aa_profile *profile, unsigned int resource, } /** - * aa_map_resouce - map compiled policy resource to internal # + * aa_map_resource - map compiled policy resource to internal # * @resource: flattened policy resource number * * Returns: resource # for the current architecture. @@ -81,10 +84,12 @@ int aa_map_resource(int resource) static int profile_setrlimit(struct aa_profile *profile, unsigned int resource, struct rlimit *new_rlim) { + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); int e = 0; - if (profile->rlimits.mask & (1 << resource) && new_rlim->rlim_max > - profile->rlimits.limits[resource].rlim_max) + if (rules->rlimits.mask & (1 << resource) && new_rlim->rlim_max > + rules->rlimits.limits[resource].rlim_max) e = -EACCES; return audit_resource(profile, resource, new_rlim->rlim_max, NULL, NULL, e); @@ -152,12 +157,15 @@ void __aa_transition_rlimits(struct aa_label *old_l, struct aa_label *new_l) * to the lesser of the tasks hard limit and the init tasks soft limit */ label_for_each_confined(i, old_l, old) { - if (old->rlimits.mask) { + struct aa_ruleset *rules = list_first_entry(&old->rules, + typeof(*rules), + list); + if (rules->rlimits.mask) { int j; for (j = 0, mask = 1; j < RLIM_NLIMITS; j++, mask <<= 1) { - if (old->rlimits.mask & mask) { + if (rules->rlimits.mask & mask) { rlim = current->signal->rlim + j; initrlim = init_task.signal->rlim + j; rlim->rlim_cur = min(rlim->rlim_max, @@ -169,17 +177,20 @@ void __aa_transition_rlimits(struct aa_label *old_l, struct aa_label *new_l) /* set any new hard limits as dictated by the new profile */ label_for_each_confined(i, new_l, new) { + struct aa_ruleset *rules = list_first_entry(&new->rules, + typeof(*rules), + list); int j; - if (!new->rlimits.mask) + if (!rules->rlimits.mask) continue; for (j = 0, mask = 1; j < RLIM_NLIMITS; j++, mask <<= 1) { - if (!(new->rlimits.mask & mask)) + if (!(rules->rlimits.mask & mask)) continue; rlim = current->signal->rlim + j; rlim->rlim_max = min(rlim->rlim_max, - new->rlimits.limits[j].rlim_max); + rules->rlimits.limits[j].rlim_max); /* soft limit should not exceed hard limit */ rlim->rlim_cur = min(rlim->rlim_cur, rlim->rlim_max); } diff --git a/security/apparmor/task.c b/security/apparmor/task.c index 503dc0877fb1..84d16a29bfcb 100644 --- a/security/apparmor/task.c +++ b/security/apparmor/task.c @@ -31,7 +31,7 @@ struct aa_label *aa_get_task_label(struct task_struct *task) struct aa_label *p; rcu_read_lock(); - p = aa_get_newest_label(__aa_task_raw_label(task)); + p = aa_get_newest_cred_label(__task_cred(task)); rcu_read_unlock(); return p; @@ -223,16 +223,18 @@ static void audit_ptrace_cb(struct audit_buffer *ab, void *va) FLAGS_NONE, GFP_ATOMIC); } -/* assumes check for PROFILE_MEDIATES is already done */ +/* assumes check for RULE_MEDIATES is already done */ /* TODO: conditionals */ static int profile_ptrace_perm(struct aa_profile *profile, struct aa_label *peer, u32 request, struct common_audit_data *sa) { + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); struct aa_perms perms = { }; aad(sa)->peer = peer; - aa_profile_match_label(profile, peer, AA_CLASS_PTRACE, request, + aa_profile_match_label(profile, rules, peer, AA_CLASS_PTRACE, request, &perms); aa_apply_modes_to_perms(profile, &perms); return aa_check_perms(profile, &perms, request, sa, audit_ptrace_cb); @@ -243,7 +245,7 @@ static int profile_tracee_perm(struct aa_profile *tracee, struct common_audit_data *sa) { if (profile_unconfined(tracee) || unconfined(tracer) || - !PROFILE_MEDIATES(tracee, AA_CLASS_PTRACE)) + !ANY_RULE_MEDIATES(&tracee->rules, AA_CLASS_PTRACE)) return 0; return profile_ptrace_perm(tracee, tracer, request, sa); @@ -256,7 +258,7 @@ static int profile_tracer_perm(struct aa_profile *tracer, if (profile_unconfined(tracer)) return 0; - if (PROFILE_MEDIATES(tracer, AA_CLASS_PTRACE)) + if (ANY_RULE_MEDIATES(&tracer->rules, AA_CLASS_PTRACE)) return profile_ptrace_perm(tracer, tracee, request, sa); /* profile uses the old style capability check for ptrace */ @@ -285,7 +287,7 @@ int aa_may_ptrace(struct aa_label *tracer, struct aa_label *tracee, { struct aa_profile *profile; u32 xrequest = request << PTRACE_PERM_SHIFT; - DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_PTRACE); + DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, AA_CLASS_PTRACE, OP_PTRACE); return xcheck_labels(tracer, tracee, profile, profile_tracer_perm(profile, tracee, request, &sa), diff --git a/security/commoncap.c b/security/commoncap.c index 5d9570f54a1c..1164278b97fd 100644 --- a/security/commoncap.c +++ b/security/commoncap.c @@ -352,14 +352,14 @@ static __u32 sansflags(__u32 m) return m & ~VFS_CAP_FLAGS_EFFECTIVE; } -static bool is_v2header(size_t size, const struct vfs_cap_data *cap) +static bool is_v2header(int size, const struct vfs_cap_data *cap) { if (size != XATTR_CAPS_SZ_2) return false; return sansflags(le32_to_cpu(cap->magic_etc)) == VFS_CAP_REVISION_2; } -static bool is_v3header(size_t size, const struct vfs_cap_data *cap) +static bool is_v3header(int size, const struct vfs_cap_data *cap) { if (size != XATTR_CAPS_SZ_3) return false; @@ -381,7 +381,7 @@ int cap_inode_getsecurity(struct user_namespace *mnt_userns, struct inode *inode, const char *name, void **buffer, bool alloc) { - int size, ret; + int size; kuid_t kroot; vfsuid_t vfsroot; u32 nsmagic, magic; @@ -398,22 +398,18 @@ int cap_inode_getsecurity(struct user_namespace *mnt_userns, dentry = d_find_any_alias(inode); if (!dentry) return -EINVAL; - - size = sizeof(struct vfs_ns_cap_data); - ret = (int)vfs_getxattr_alloc(mnt_userns, dentry, XATTR_NAME_CAPS, - &tmpbuf, size, GFP_NOFS); + size = vfs_getxattr_alloc(mnt_userns, dentry, XATTR_NAME_CAPS, &tmpbuf, + sizeof(struct vfs_ns_cap_data), GFP_NOFS); dput(dentry); - - if (ret < 0 || !tmpbuf) { - size = ret; + /* gcc11 complains if we don't check for !tmpbuf */ + if (size < 0 || !tmpbuf) goto out_free; - } fs_ns = inode->i_sb->s_user_ns; cap = (struct vfs_cap_data *) tmpbuf; - if (is_v2header((size_t) ret, cap)) { + if (is_v2header(size, cap)) { root = 0; - } else if (is_v3header((size_t) ret, cap)) { + } else if (is_v3header(size, cap)) { nscap = (struct vfs_ns_cap_data *) tmpbuf; root = le32_to_cpu(nscap->rootid); } else { diff --git a/security/device_cgroup.c b/security/device_cgroup.c index a9f8c63a96d1..bef2b9285fb3 100644 --- a/security/device_cgroup.c +++ b/security/device_cgroup.c @@ -82,6 +82,17 @@ free_and_exit: return -ENOMEM; } +static void dev_exceptions_move(struct list_head *dest, struct list_head *orig) +{ + struct dev_exception_item *ex, *tmp; + + lockdep_assert_held(&devcgroup_mutex); + + list_for_each_entry_safe(ex, tmp, orig, list) { + list_move_tail(&ex->list, dest); + } +} + /* * called under devcgroup_mutex */ @@ -604,11 +615,13 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, int count, rc = 0; struct dev_exception_item ex; struct dev_cgroup *parent = css_to_devcgroup(devcgroup->css.parent); + struct dev_cgroup tmp_devcgrp; if (!capable(CAP_SYS_ADMIN)) return -EPERM; memset(&ex, 0, sizeof(ex)); + memset(&tmp_devcgrp, 0, sizeof(tmp_devcgrp)); b = buffer; switch (*b) { @@ -620,15 +633,27 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, if (!may_allow_all(parent)) return -EPERM; - dev_exception_clean(devcgroup); - devcgroup->behavior = DEVCG_DEFAULT_ALLOW; - if (!parent) + if (!parent) { + devcgroup->behavior = DEVCG_DEFAULT_ALLOW; + dev_exception_clean(devcgroup); break; + } + INIT_LIST_HEAD(&tmp_devcgrp.exceptions); + rc = dev_exceptions_copy(&tmp_devcgrp.exceptions, + &devcgroup->exceptions); + if (rc) + return rc; + dev_exception_clean(devcgroup); rc = dev_exceptions_copy(&devcgroup->exceptions, &parent->exceptions); - if (rc) + if (rc) { + dev_exceptions_move(&devcgroup->exceptions, + &tmp_devcgrp.exceptions); return rc; + } + devcgroup->behavior = DEVCG_DEFAULT_ALLOW; + dev_exception_clean(&tmp_devcgrp); break; case DEVCG_DENY: if (css_has_online_children(&devcgroup->css)) diff --git a/security/integrity/digsig.c b/security/integrity/digsig.c index 8a82a6c7f48a..f2193c531f4a 100644 --- a/security/integrity/digsig.c +++ b/security/integrity/digsig.c @@ -126,6 +126,7 @@ int __init integrity_init_keyring(const unsigned int id) { struct key_restriction *restriction; key_perm_t perm; + int ret; perm = (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_VIEW | KEY_USR_READ | KEY_USR_SEARCH; @@ -154,7 +155,10 @@ int __init integrity_init_keyring(const unsigned int id) perm |= KEY_USR_WRITE; out: - return __integrity_init_keyring(id, perm, restriction); + ret = __integrity_init_keyring(id, perm, restriction); + if (ret) + kfree(restriction); + return ret; } static int __init integrity_add_key(const unsigned int id, const void *data, diff --git a/security/integrity/evm/evm_crypto.c b/security/integrity/evm/evm_crypto.c index 708de9656bbd..fa5ff13fa8c9 100644 --- a/security/integrity/evm/evm_crypto.c +++ b/security/integrity/evm/evm_crypto.c @@ -335,14 +335,15 @@ static int evm_is_immutable(struct dentry *dentry, struct inode *inode) (char **)&xattr_data, 0, GFP_NOFS); if (rc <= 0) { if (rc == -ENODATA) - return 0; - return rc; + rc = 0; + goto out; } if (xattr_data->type == EVM_XATTR_PORTABLE_DIGSIG) rc = 1; else rc = 0; +out: kfree(xattr_data); return rc; } diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index e01cfd4ad896..f02e609460e2 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -455,14 +455,17 @@ static int evm_xattr_change(struct user_namespace *mnt_userns, rc = vfs_getxattr_alloc(&init_user_ns, dentry, xattr_name, &xattr_data, 0, GFP_NOFS); - if (rc < 0) - return 1; + if (rc < 0) { + rc = 1; + goto out; + } if (rc == xattr_value_len) rc = !!memcmp(xattr_value, xattr_data, rc); else rc = 1; +out: kfree(xattr_data); return rc; } diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index 7249f16257c7..39caeca47444 100644 --- a/security/integrity/ima/Kconfig +++ b/security/integrity/ima/Kconfig @@ -112,7 +112,7 @@ choice config IMA_DEFAULT_HASH_SM3 bool "SM3" - depends on CRYPTO_SM3=y + depends on CRYPTO_SM3_GENERIC=y endchoice config IMA_DEFAULT_HASH diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index be965a8715e4..03b440921e61 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -326,7 +326,7 @@ enum integrity_status ima_get_cache_status(struct integrity_iint_cache *iint, enum hash_algo ima_get_hash_algo(const struct evm_ima_xattr_data *xattr_value, int xattr_len); int ima_read_xattr(struct dentry *dentry, - struct evm_ima_xattr_data **xattr_value); + struct evm_ima_xattr_data **xattr_value, int xattr_len); #else static inline int ima_check_blacklist(struct integrity_iint_cache *iint, @@ -372,7 +372,8 @@ ima_get_hash_algo(struct evm_ima_xattr_data *xattr_value, int xattr_len) } static inline int ima_read_xattr(struct dentry *dentry, - struct evm_ima_xattr_data **xattr_value) + struct evm_ima_xattr_data **xattr_value, + int xattr_len) { return 0; } diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index 3c9af3dc0713..ee6f7e237f2e 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -221,12 +221,12 @@ enum hash_algo ima_get_hash_algo(const struct evm_ima_xattr_data *xattr_value, } int ima_read_xattr(struct dentry *dentry, - struct evm_ima_xattr_data **xattr_value) + struct evm_ima_xattr_data **xattr_value, int xattr_len) { - ssize_t ret; + int ret; ret = vfs_getxattr_alloc(&init_user_ns, dentry, XATTR_NAME_IMA, - (char **)xattr_value, 0, GFP_NOFS); + (char **)xattr_value, xattr_len, GFP_NOFS); if (ret == -EOPNOTSUPP) ret = 0; return ret; diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 040b03ddc1c7..377300973e6c 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -293,7 +293,8 @@ static int process_measurement(struct file *file, const struct cred *cred, /* HASH sets the digital signature and update flags, nothing else */ if ((action & IMA_HASH) && !(test_bit(IMA_DIGSIG, &iint->atomic_flags))) { - xattr_len = ima_read_xattr(file_dentry(file), &xattr_value); + xattr_len = ima_read_xattr(file_dentry(file), + &xattr_value, xattr_len); if ((xattr_value && xattr_len > 2) && (xattr_value->type == EVM_IMA_XATTR_DIGSIG)) set_bit(IMA_DIGSIG, &iint->atomic_flags); @@ -316,7 +317,8 @@ static int process_measurement(struct file *file, const struct cred *cred, if ((action & IMA_APPRAISE_SUBMASK) || strcmp(template_desc->name, IMA_TEMPLATE_IMA_NAME) != 0) { /* read 'security.ima' */ - xattr_len = ima_read_xattr(file_dentry(file), &xattr_value); + xattr_len = ima_read_xattr(file_dentry(file), + &xattr_value, xattr_len); /* * Read the appended modsig if allowed by the policy, and allow @@ -542,8 +544,13 @@ static int __ima_inode_hash(struct inode *inode, struct file *file, char *buf, rc = ima_collect_measurement(&tmp_iint, file, NULL, 0, ima_hash_algo, NULL); - if (rc < 0) + if (rc < 0) { + /* ima_hash could be allocated in case of failure. */ + if (rc != -ENOMEM) + kfree(tmp_iint.ima_hash); + return -EOPNOTSUPP; + } iint = &tmp_iint; mutex_lock(&iint->mutex); diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index edd95ba02c11..6a68ec270822 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -422,12 +422,6 @@ static struct ima_rule_entry *ima_lsm_copy_rule(struct ima_rule_entry *entry) nentry->lsm[i].type = entry->lsm[i].type; nentry->lsm[i].args_p = entry->lsm[i].args_p; - /* - * Remove the reference from entry so that the associated - * memory will not be freed during a later call to - * ima_lsm_free_rule(entry). - */ - entry->lsm[i].args_p = NULL; ima_filter_rule_init(nentry->lsm[i].type, Audit_equal, nentry->lsm[i].args_p, @@ -441,6 +435,7 @@ static struct ima_rule_entry *ima_lsm_copy_rule(struct ima_rule_entry *entry) static int ima_lsm_update_rule(struct ima_rule_entry *entry) { + int i; struct ima_rule_entry *nentry; nentry = ima_lsm_copy_rule(entry); @@ -455,7 +450,8 @@ static int ima_lsm_update_rule(struct ima_rule_entry *entry) * references and the entry itself. All other memory references will now * be owned by nentry. */ - ima_lsm_free_rule(entry); + for (i = 0; i < MAX_LSM_RULES; i++) + ima_filter_rule_free(entry->lsm[i].rule); kfree(entry); return 0; @@ -573,6 +569,9 @@ static bool ima_match_rules(struct ima_rule_entry *rule, const char *func_data) { int i; + bool result = false; + struct ima_rule_entry *lsm_rule = rule; + bool rule_reinitialized = false; if ((rule->flags & IMA_FUNC) && (rule->func != func && func != POST_SETATTR)) @@ -636,35 +635,55 @@ static bool ima_match_rules(struct ima_rule_entry *rule, int rc = 0; u32 osid; - if (!rule->lsm[i].rule) { - if (!rule->lsm[i].args_p) + if (!lsm_rule->lsm[i].rule) { + if (!lsm_rule->lsm[i].args_p) continue; else return false; } + +retry: switch (i) { case LSM_OBJ_USER: case LSM_OBJ_ROLE: case LSM_OBJ_TYPE: security_inode_getsecid(inode, &osid); - rc = ima_filter_rule_match(osid, rule->lsm[i].type, + rc = ima_filter_rule_match(osid, lsm_rule->lsm[i].type, Audit_equal, - rule->lsm[i].rule); + lsm_rule->lsm[i].rule); break; case LSM_SUBJ_USER: case LSM_SUBJ_ROLE: case LSM_SUBJ_TYPE: - rc = ima_filter_rule_match(secid, rule->lsm[i].type, + rc = ima_filter_rule_match(secid, lsm_rule->lsm[i].type, Audit_equal, - rule->lsm[i].rule); + lsm_rule->lsm[i].rule); break; default: break; } - if (!rc) - return false; + + if (rc == -ESTALE && !rule_reinitialized) { + lsm_rule = ima_lsm_copy_rule(rule); + if (lsm_rule) { + rule_reinitialized = true; + goto retry; + } + } + if (!rc) { + result = false; + goto out; + } } - return true; + result = true; + +out: + if (rule_reinitialized) { + for (i = 0; i < MAX_LSM_RULES; i++) + ima_filter_rule_free(lsm_rule->lsm[i].rule); + kfree(lsm_rule); + } + return result; } /* diff --git a/security/integrity/ima/ima_template.c b/security/integrity/ima/ima_template.c index c25079faa208..04c49f05cb74 100644 --- a/security/integrity/ima/ima_template.c +++ b/security/integrity/ima/ima_template.c @@ -245,11 +245,11 @@ int template_desc_init_fields(const char *template_fmt, } if (fields && num_fields) { - *fields = kmalloc_array(i, sizeof(*fields), GFP_KERNEL); + *fields = kmalloc_array(i, sizeof(**fields), GFP_KERNEL); if (*fields == NULL) return -ENOMEM; - memcpy(*fields, found_fields, i * sizeof(*fields)); + memcpy(*fields, found_fields, i * sizeof(**fields)); *num_fields = i; } @@ -340,8 +340,11 @@ static struct ima_template_desc *restore_template_fmt(char *template_name) template_desc->name = ""; template_desc->fmt = kstrdup(template_name, GFP_KERNEL); - if (!template_desc->fmt) + if (!template_desc->fmt) { + kfree(template_desc); + template_desc = NULL; goto out; + } spin_lock(&template_list); list_add_tail_rcu(&template_desc->list, &defined_templates); diff --git a/security/integrity/ima/ima_template_lib.c b/security/integrity/ima/ima_template_lib.c index 7bf9b1507220..4564faae7d67 100644 --- a/security/integrity/ima/ima_template_lib.c +++ b/security/integrity/ima/ima_template_lib.c @@ -601,16 +601,15 @@ int ima_eventevmsig_init(struct ima_event_data *event_data, rc = vfs_getxattr_alloc(&init_user_ns, file_dentry(event_data->file), XATTR_NAME_EVM, (char **)&xattr_data, 0, GFP_NOFS); - if (rc <= 0) - return 0; - - if (xattr_data->type != EVM_XATTR_PORTABLE_DIGSIG) { - kfree(xattr_data); - return 0; + if (rc <= 0 || xattr_data->type != EVM_XATTR_PORTABLE_DIGSIG) { + rc = 0; + goto out; } rc = ima_write_template_field_data((char *)xattr_data, rc, DATA_FMT_HEX, field_data); + +out: kfree(xattr_data); return rc; } diff --git a/security/integrity/platform_certs/load_uefi.c b/security/integrity/platform_certs/load_uefi.c index b78753d27d8e..d1fdd113450a 100644 --- a/security/integrity/platform_certs/load_uefi.c +++ b/security/integrity/platform_certs/load_uefi.c @@ -35,6 +35,7 @@ static const struct dmi_system_id uefi_skip_cert[] = { { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "MacPro7,1") }, { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "iMac20,1") }, { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "iMac20,2") }, + { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "iMacPro1,1") }, { } }; diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c index e05cfc2e49ae..1e313982af02 100644 --- a/security/keys/encrypted-keys/encrypted.c +++ b/security/keys/encrypted-keys/encrypted.c @@ -627,7 +627,7 @@ static struct encrypted_key_payload *encrypted_key_alloc(struct key *key, pr_err("encrypted key: instantiation of keys using provided decrypted data is disabled since CONFIG_USER_DECRYPTED_DATA is set to false\n"); return ERR_PTR(-EINVAL); } - if (strlen(decrypted_data) != decrypted_datalen) { + if (strlen(decrypted_data) != decrypted_datalen * 2) { pr_err("encrypted key: decrypted data provided does not match decrypted data length provided\n"); return ERR_PTR(-EINVAL); } @@ -791,8 +791,8 @@ static int encrypted_init(struct encrypted_key_payload *epayload, ret = encrypted_key_decrypt(epayload, format, hex_encoded_iv); } else if (decrypted_data) { get_random_bytes(epayload->iv, ivsize); - memcpy(epayload->decrypted_data, decrypted_data, - epayload->decrypted_datalen); + ret = hex2bin(epayload->decrypted_data, decrypted_data, + epayload->decrypted_datalen); } else { get_random_bytes(epayload->iv, ivsize); get_random_bytes(epayload->decrypted_data, epayload->decrypted_datalen); diff --git a/security/landlock/fs.c b/security/landlock/fs.c index 64ed7665455f..adcea0fe7e68 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -146,7 +146,8 @@ retry: #define ACCESS_FILE ( \ LANDLOCK_ACCESS_FS_EXECUTE | \ LANDLOCK_ACCESS_FS_WRITE_FILE | \ - LANDLOCK_ACCESS_FS_READ_FILE) + LANDLOCK_ACCESS_FS_READ_FILE | \ + LANDLOCK_ACCESS_FS_TRUNCATE) /* clang-format on */ /* @@ -297,6 +298,19 @@ get_handled_accesses(const struct landlock_ruleset *const domain) return access_dom & LANDLOCK_MASK_ACCESS_FS; } +/** + * 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, @@ -430,7 +444,7 @@ is_eacces(const layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS], } /** - * check_access_path_dual - Check accesses for requests with a common path + * is_access_to_paths_allowed - Check accesses for requests with a common path * * @domain: Domain to check against. * @path: File hierarchy to walk through. @@ -465,14 +479,10 @@ is_eacces(const layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS], * allow the request. * * Returns: - * - 0 if the access request is granted; - * - -EACCES if it is denied because of access right other than - * LANDLOCK_ACCESS_FS_REFER; - * - -EXDEV if the renaming or linking would be a privileged escalation - * (according to each layered policies), or if LANDLOCK_ACCESS_FS_REFER is - * not allowed by the source or the destination. + * - true if the access request is granted; + * - false otherwise. */ -static int check_access_path_dual( +static bool is_access_to_paths_allowed( const struct landlock_ruleset *const domain, const struct path *const path, const access_mask_t access_request_parent1, @@ -492,17 +502,17 @@ static int check_access_path_dual( (*layer_masks_child2)[LANDLOCK_NUM_ACCESS_FS] = NULL; if (!access_request_parent1 && !access_request_parent2) - return 0; + return true; if (WARN_ON_ONCE(!domain || !path)) - return 0; + return true; if (is_nouser_or_private(path->dentry)) - return 0; + return true; if (WARN_ON_ONCE(domain->num_layers < 1 || !layer_masks_parent1)) - return -EACCES; + return false; if (unlikely(layer_masks_parent2)) { if (WARN_ON_ONCE(!dentry_child1)) - return -EACCES; + return false; /* * For a double request, first check for potential privilege * escalation by looking at domain handled accesses (which are @@ -513,7 +523,7 @@ static int check_access_path_dual( is_dom_check = true; } else { if (WARN_ON_ONCE(dentry_child1 || dentry_child2)) - return -EACCES; + return false; /* For a simple request, only check for requested accesses. */ access_masked_parent1 = access_request_parent1; access_masked_parent2 = access_request_parent2; @@ -622,24 +632,7 @@ jump_up: } path_put(&walker_path); - if (allowed_parent1 && allowed_parent2) - return 0; - - /* - * This prioritizes EACCES over EXDEV for all actions, including - * renames with RENAME_EXCHANGE. - */ - if (likely(is_eacces(layer_masks_parent1, access_request_parent1) || - is_eacces(layer_masks_parent2, access_request_parent2))) - return -EACCES; - - /* - * Gracefully forbids reparenting if the destination directory - * hierarchy is not a superset of restrictions of the source directory - * hierarchy, or if LANDLOCK_ACCESS_FS_REFER is not allowed by the - * source or the destination. - */ - return -EXDEV; + return allowed_parent1 && allowed_parent2; } static inline int check_access_path(const struct landlock_ruleset *const domain, @@ -649,8 +642,10 @@ 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); - return check_access_path_dual(domain, path, access_request, - &layer_masks, NULL, 0, NULL, NULL); + if (is_access_to_paths_allowed(domain, path, access_request, + &layer_masks, NULL, 0, NULL, NULL)) + return 0; + return -EACCES; } static inline int current_check_access_path(const struct path *const path, @@ -711,8 +706,9 @@ static inline access_mask_t maybe_remove(const struct dentry *const dentry) * file. While walking from @dir to @mnt_root, we record all the domain's * allowed accesses in @layer_masks_dom. * - * This is similar to check_access_path_dual() but much simpler because it only - * handles walking on the same mount point and only checks one set of accesses. + * This is similar to is_access_to_paths_allowed() but much simpler because it + * only handles walking on the same mount point and only checks one set of + * accesses. * * Returns: * - true if all the domain access rights are allowed for @dir; @@ -857,10 +853,11 @@ static int current_check_refer_path(struct dentry *const old_dentry, access_request_parent1 = init_layer_masks( dom, access_request_parent1 | access_request_parent2, &layer_masks_parent1); - return check_access_path_dual(dom, new_dir, - access_request_parent1, - &layer_masks_parent1, NULL, 0, - NULL, NULL); + if (is_access_to_paths_allowed( + dom, new_dir, access_request_parent1, + &layer_masks_parent1, NULL, 0, NULL, NULL)) + return 0; + return -EACCES; } access_request_parent1 |= LANDLOCK_ACCESS_FS_REFER; @@ -886,11 +883,27 @@ static int current_check_refer_path(struct dentry *const old_dentry, * parent access rights. This will be useful to compare with the * destination parent access rights. */ - return check_access_path_dual(dom, &mnt_dir, access_request_parent1, - &layer_masks_parent1, old_dentry, - access_request_parent2, - &layer_masks_parent2, - exchange ? new_dentry : NULL); + if (is_access_to_paths_allowed( + dom, &mnt_dir, access_request_parent1, &layer_masks_parent1, + old_dentry, access_request_parent2, &layer_masks_parent2, + exchange ? new_dentry : NULL)) + return 0; + + /* + * This prioritizes EACCES over EXDEV for all actions, including + * renames with RENAME_EXCHANGE. + */ + if (likely(is_eacces(&layer_masks_parent1, access_request_parent1) || + is_eacces(&layer_masks_parent2, access_request_parent2))) + return -EACCES; + + /* + * Gracefully forbids reparenting if the destination directory + * hierarchy is not a superset of restrictions of the source directory + * hierarchy, or if LANDLOCK_ACCESS_FS_REFER is not allowed by the + * source or the destination. + */ + return -EXDEV; } /* Inode hooks */ @@ -1142,9 +1155,23 @@ static int hook_path_rmdir(const struct path *const dir, return current_check_access_path(dir, LANDLOCK_ACCESS_FS_REMOVE_DIR); } +static int hook_path_truncate(const struct path *const path) +{ + return current_check_access_path(path, LANDLOCK_ACCESS_FS_TRUNCATE); +} + /* File hooks */ -static inline access_mask_t get_file_access(const struct file *const file) +/** + * get_required_file_open_access - Get access needed to open a file + * + * @file: File being opened. + * + * Returns the access rights that are required for opening the given file, + * depending on the file type and open mode. + */ +static inline access_mask_t +get_required_file_open_access(const struct file *const file) { access_mask_t access = 0; @@ -1162,19 +1189,95 @@ static inline access_mask_t get_file_access(const struct file *const file) return access; } +static int hook_file_alloc_security(struct file *const file) +{ + /* + * Grants all access rights, even if most of them are not checked later + * on. It is more consistent. + * + * Notably, file descriptors for regular files can also be acquired + * without going through the file_open hook, for example when using + * memfd_create(2). + */ + landlock_file(file)->allowed_access = LANDLOCK_MASK_ACCESS_FS; + return 0; +} + 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(); if (!dom) return 0; + /* - * Because a file may be opened with O_PATH, get_file_access() may - * return 0. This case will be handled with a future Landlock + * Because a file may be opened with O_PATH, get_required_file_open_access() + * may return 0. This case will be handled with a future Landlock * evolution. */ - return check_access_path(dom, &file->f_path, get_file_access(file)); + open_access_request = get_required_file_open_access(file); + + /* + * We look up more access than what we immediately need for open(), so + * that we can later authorize operations on opened files. + */ + full_access_request = open_access_request | optional_access; + + if (is_access_to_paths_allowed( + dom, &file->f_path, + init_layer_masks(dom, full_access_request, &layer_masks), + &layer_masks, NULL, 0, NULL, NULL)) { + allowed_access = full_access_request; + } else { + unsigned long access_bit; + const unsigned long access_req = full_access_request; + + /* + * Calculate the actual allowed access rights from layer_masks. + * Add each access right to allowed_access which has not been + * vetoed by any layer. + */ + allowed_access = 0; + for_each_set_bit(access_bit, &access_req, + ARRAY_SIZE(layer_masks)) { + if (!layer_masks[access_bit]) + allowed_access |= BIT_ULL(access_bit); + } + } + + /* + * For operations on already opened files (i.e. ftruncate()), it is the + * access rights at the time of open() which decide whether the + * operation is permitted. Therefore, we record the relevant subset of + * file access rights in the opened struct file. + */ + landlock_file(file)->allowed_access = allowed_access; + + if ((open_access_request & allowed_access) == open_access_request) + return 0; + + return -EACCES; +} + +static int hook_file_truncate(struct file *const file) +{ + /* + * Allows truncation if the truncate right was available at the time of + * opening the file, to get a consistent access check as for read, write + * and execute operations. + * + * Note: For checks done based on the file's Landlock allowed access, we + * enforce them independently of whether the current thread is in a + * Landlock domain, so that open files passed between independent + * processes retain their behaviour. + */ + if (landlock_file(file)->allowed_access & LANDLOCK_ACCESS_FS_TRUNCATE) + return 0; + return -EACCES; } static struct security_hook_list landlock_hooks[] __lsm_ro_after_init = { @@ -1194,8 +1297,11 @@ static struct security_hook_list landlock_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(path_symlink, hook_path_symlink), LSM_HOOK_INIT(path_unlink, hook_path_unlink), LSM_HOOK_INIT(path_rmdir, hook_path_rmdir), + LSM_HOOK_INIT(path_truncate, hook_path_truncate), + LSM_HOOK_INIT(file_alloc_security, hook_file_alloc_security), LSM_HOOK_INIT(file_open, hook_file_open), + LSM_HOOK_INIT(file_truncate, hook_file_truncate), }; __init void landlock_add_fs_hooks(void) diff --git a/security/landlock/fs.h b/security/landlock/fs.h index 8db7acf9109b..488e4813680a 100644 --- a/security/landlock/fs.h +++ b/security/landlock/fs.h @@ -37,6 +37,24 @@ struct landlock_inode_security { }; /** + * struct landlock_file_security - File security blob + * + * This information is populated when opening a file in hook_file_open, and + * tracks the relevant Landlock access rights that were available at the time + * of opening the file. Other LSM hooks use these rights in order to authorize + * operations on already opened files. + */ +struct landlock_file_security { + /** + * @allowed_access: Access rights that were available at the time of + * opening the file. This is not necessarily the full set of access + * rights available at that time, but it's the necessary subset as + * needed to authorize later operations on the open file. + */ + access_mask_t allowed_access; +}; + +/** * struct landlock_superblock_security - Superblock security blob * * Enable hook_sb_delete() to wait for concurrent calls to release_inode(). @@ -50,6 +68,12 @@ struct landlock_superblock_security { atomic_long_t inode_refs; }; +static inline struct landlock_file_security * +landlock_file(const struct file *const file) +{ + return file->f_security + landlock_blob_sizes.lbs_file; +} + static inline struct landlock_inode_security * landlock_inode(const struct inode *const inode) { diff --git a/security/landlock/limits.h b/security/landlock/limits.h index b54184ab9439..82288f0e9e5e 100644 --- a/security/landlock/limits.h +++ b/security/landlock/limits.h @@ -18,7 +18,7 @@ #define LANDLOCK_MAX_NUM_LAYERS 16 #define LANDLOCK_MAX_NUM_RULES U32_MAX -#define LANDLOCK_LAST_ACCESS_FS LANDLOCK_ACCESS_FS_REFER +#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) diff --git a/security/landlock/setup.c b/security/landlock/setup.c index f8e8e980454c..3f196d2ce4f9 100644 --- a/security/landlock/setup.c +++ b/security/landlock/setup.c @@ -19,6 +19,7 @@ bool landlock_initialized __lsm_ro_after_init = false; struct lsm_blob_sizes landlock_blob_sizes __lsm_ro_after_init = { .lbs_cred = sizeof(struct landlock_cred_security), + .lbs_file = sizeof(struct landlock_file_security), .lbs_inode = sizeof(struct landlock_inode_security), .lbs_superblock = sizeof(struct landlock_superblock_security), }; diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index 2ca0ccbd905a..245cc650a4dc 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -129,7 +129,7 @@ static const struct file_operations ruleset_fops = { .write = fop_dummy_write, }; -#define LANDLOCK_ABI_VERSION 2 +#define LANDLOCK_ABI_VERSION 3 /** * sys_landlock_create_ruleset - Create a new ruleset diff --git a/security/lsm_audit.c b/security/lsm_audit.c index 75cc3f8d2a42..a7355b4b9bb8 100644 --- a/security/lsm_audit.c +++ b/security/lsm_audit.c @@ -190,6 +190,7 @@ static inline void print_ipv4_addr(struct audit_buffer *ab, __be32 addr, /** * dump_common_audit_data - helper to dump common audit data + * @ab : the audit buffer * @a : common audit data * */ diff --git a/security/security.c b/security/security.c index bdc295ad5fba..d1571900a8c7 100644 --- a/security/security.c +++ b/security/security.c @@ -161,8 +161,8 @@ static void __init append_ordered_lsm(struct lsm_info *lsm, const char *from) lsm->enabled = &lsm_enabled_true; ordered_lsms[last_lsm++] = lsm; - init_debug("%s ordering: %s (%sabled)\n", from, lsm->name, - is_enabled(lsm) ? "en" : "dis"); + init_debug("%s ordered: %s (%s)\n", from, lsm->name, + is_enabled(lsm) ? "enabled" : "disabled"); } /* Is an LSM allowed to be initialized? */ @@ -185,11 +185,12 @@ static void __init lsm_set_blob_size(int *need, int *lbs) { int offset; - if (*need > 0) { - offset = *lbs; - *lbs += *need; - *need = offset; - } + if (*need <= 0) + return; + + offset = ALIGN(*lbs, sizeof(void *)); + *lbs = offset + *need; + *need = offset; } static void __init lsm_set_blob_sizes(struct lsm_blob_sizes *needed) @@ -224,7 +225,7 @@ static void __init prepare_lsm(struct lsm_info *lsm) if (enabled) { if ((lsm->flags & LSM_FLAG_EXCLUSIVE) && !exclusive) { exclusive = lsm; - init_debug("exclusive chosen: %s\n", lsm->name); + init_debug("exclusive chosen: %s\n", lsm->name); } lsm_set_blob_sizes(lsm->blobs); @@ -252,7 +253,7 @@ static void __init ordered_lsm_parse(const char *order, const char *origin) /* LSM_ORDER_FIRST is always first. */ for (lsm = __start_lsm_info; lsm < __end_lsm_info; lsm++) { if (lsm->order == LSM_ORDER_FIRST) - append_ordered_lsm(lsm, "first"); + append_ordered_lsm(lsm, " first"); } /* Process "security=", if given. */ @@ -270,7 +271,7 @@ static void __init ordered_lsm_parse(const char *order, const char *origin) if ((major->flags & LSM_FLAG_LEGACY_MAJOR) && strcmp(major->name, chosen_major_lsm) != 0) { set_enabled(major, false); - init_debug("security=%s disabled: %s\n", + init_debug("security=%s disabled: %s (only one legacy major LSM)\n", chosen_major_lsm, major->name); } } @@ -291,7 +292,8 @@ static void __init ordered_lsm_parse(const char *order, const char *origin) } if (!found) - init_debug("%s ignored: %s\n", origin, name); + init_debug("%s ignored: %s (not built into kernel)\n", + origin, name); } /* Process "security=", if given. */ @@ -309,7 +311,8 @@ static void __init ordered_lsm_parse(const char *order, const char *origin) if (exists_ordered_lsm(lsm)) continue; set_enabled(lsm, false); - init_debug("%s disabled: %s\n", origin, lsm->name); + init_debug("%s skipped: %s (not in requested order)\n", + origin, lsm->name); } kfree(sep); @@ -320,6 +323,24 @@ static void __init lsm_early_task(struct task_struct *task); static int lsm_append(const char *new, char **result); +static void __init report_lsm_order(void) +{ + struct lsm_info **lsm, *early; + int first = 0; + + pr_info("initializing lsm="); + + /* Report each enabled LSM name, comma separated. */ + for (early = __start_early_lsm_info; early < __end_early_lsm_info; early++) + if (is_enabled(early)) + pr_cont("%s%s", first++ == 0 ? "" : ",", early->name); + for (lsm = ordered_lsms; *lsm; lsm++) + if (is_enabled(*lsm)) + pr_cont("%s%s", first++ == 0 ? "" : ",", (*lsm)->name); + + pr_cont("\n"); +} + static void __init ordered_lsm_init(void) { struct lsm_info **lsm; @@ -329,7 +350,8 @@ static void __init ordered_lsm_init(void) if (chosen_lsm_order) { if (chosen_major_lsm) { - pr_info("security= is ignored because it is superseded by lsm=\n"); + pr_warn("security=%s is ignored because it is superseded by lsm=%s\n", + chosen_major_lsm, chosen_lsm_order); chosen_major_lsm = NULL; } ordered_lsm_parse(chosen_lsm_order, "cmdline"); @@ -339,6 +361,8 @@ static void __init ordered_lsm_init(void) for (lsm = ordered_lsms; *lsm; lsm++) prepare_lsm(*lsm); + report_lsm_order(); + init_debug("cred blob size = %d\n", blob_sizes.lbs_cred); init_debug("file blob size = %d\n", blob_sizes.lbs_file); init_debug("inode blob size = %d\n", blob_sizes.lbs_inode); @@ -395,13 +419,17 @@ int __init security_init(void) { struct lsm_info *lsm; - pr_info("Security Framework initializing\n"); + init_debug("legacy security=%s\n", chosen_major_lsm ?: " *unspecified*"); + init_debug(" CONFIG_LSM=%s\n", builtin_lsm_order); + init_debug("boot arg lsm=%s\n", chosen_lsm_order ?: " *unspecified*"); /* * Append the names of the early LSM modules now that kmalloc() is * available */ for (lsm = __start_early_lsm_info; lsm < __end_early_lsm_info; lsm++) { + init_debug(" early started: %s (%s)\n", lsm->name, + is_enabled(lsm) ? "enabled" : "disabled"); if (lsm->enabled) lsm_append(lsm->name, &lsm_names); } @@ -1694,6 +1722,11 @@ int security_file_open(struct file *file) return fsnotify_perm(file, MAY_OPEN); } +int security_file_truncate(struct file *file) +{ + return call_int_hook(file_truncate, 0, file); +} + int security_task_alloc(struct task_struct *task, unsigned long clone_flags) { int rc = lsm_task_alloc(task); @@ -2309,11 +2342,11 @@ int security_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) } EXPORT_SYMBOL(security_sock_rcv_skb); -int security_socket_getpeersec_stream(struct socket *sock, char __user *optval, - int __user *optlen, unsigned len) +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); + optval, optlen, len); } int security_socket_getpeersec_dgram(struct socket *sock, struct sk_buff *skb, u32 *secid) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 7c5c8d17695c..3c5be76a9199 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -5138,11 +5138,12 @@ static int selinux_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) return err; } -static int selinux_socket_getpeersec_stream(struct socket *sock, char __user *optval, - int __user *optlen, unsigned len) +static int selinux_socket_getpeersec_stream(struct socket *sock, + sockptr_t optval, sockptr_t optlen, + unsigned int len) { int err = 0; - char *scontext; + char *scontext = NULL; u32 scontext_len; struct sk_security_struct *sksec = sock->sk->sk_security; u32 peer_sid = SECSID_NULL; @@ -5158,17 +5159,15 @@ static int selinux_socket_getpeersec_stream(struct socket *sock, char __user *op &scontext_len); if (err) return err; - if (scontext_len > len) { err = -ERANGE; goto out_len; } - if (copy_to_user(optval, scontext, scontext_len)) + if (copy_to_sockptr(optval, scontext, scontext_len)) err = -EFAULT; - out_len: - if (put_user(scontext_len, optlen)) + if (copy_to_sockptr(optlen, &scontext_len, sizeof(scontext_len))) err = -EFAULT; kfree(scontext); return err; diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index a00d19139436..0a6894cdc54d 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -294,7 +294,7 @@ static ssize_t sel_write_disable(struct file *file, const char __user *buf, */ pr_err("SELinux: Runtime disable is deprecated, use selinux=0 on the kernel cmdline.\n"); pr_err("SELinux: https://github.com/SELinuxProject/selinux-kernel/wiki/DEPRECATE-runtime-disable\n"); - ssleep(5); + ssleep(15); if (count >= PAGE_SIZE) return -ENOMEM; @@ -763,7 +763,7 @@ static ssize_t sel_write_checkreqprot(struct file *file, const char __user *buf, checkreqprot_set(fsi->state, (new_value ? 1 : 0)); if (new_value) - ssleep(5); + ssleep(15); length = count; selinux_ima_measure_state(fsi->state); diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index 64a6a37dc36d..0092b29022f5 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -68,12 +68,6 @@ #include "policycap_names.h" #include "ima.h" -struct convert_context_args { - struct selinux_state *state; - struct policydb *oldp; - struct policydb *newp; -}; - struct selinux_policy_convert_data { struct convert_context_args args; struct sidtab_convert_params sidtab_params; @@ -2014,18 +2008,22 @@ static inline int convert_context_handle_invalid_context( return 0; } -/* - * Convert the values in the security context - * structure `oldc' from the values specified - * in the policy `p->oldp' to the values specified - * in the policy `p->newp', storing the new context - * in `newc'. Verify that the context is valid - * under the new policy. +/** + * services_convert_context - Convert a security context across policies. + * @args: populated convert_context_args struct + * @oldc: original context + * @newc: converted context + * @gfp_flags: allocation flags + * + * Convert the values in the security context structure @oldc from the values + * specified in the policy @args->oldp to the values specified in the policy + * @args->newp, storing the new context in @newc, and verifying that the + * context is valid under the new policy. */ -static int convert_context(struct context *oldc, struct context *newc, void *p, - gfp_t gfp_flags) +int services_convert_context(struct convert_context_args *args, + struct context *oldc, struct context *newc, + gfp_t gfp_flags) { - struct convert_context_args *args; struct ocontext *oc; struct role_datum *role; struct type_datum *typdatum; @@ -2034,15 +2032,12 @@ static int convert_context(struct context *oldc, struct context *newc, void *p, u32 len; int rc; - args = p; - if (oldc->str) { s = kstrdup(oldc->str, gfp_flags); if (!s) return -ENOMEM; - rc = string_to_context_struct(args->newp, NULL, s, - newc, SECSID_NULL); + rc = string_to_context_struct(args->newp, NULL, s, newc, SECSID_NULL); if (rc == -EINVAL) { /* * Retain string representation for later mapping. @@ -2073,8 +2068,7 @@ static int convert_context(struct context *oldc, struct context *newc, void *p, /* Convert the user. */ usrdatum = symtab_search(&args->newp->p_users, - sym_name(args->oldp, - SYM_USERS, oldc->user - 1)); + sym_name(args->oldp, SYM_USERS, oldc->user - 1)); if (!usrdatum) goto bad; newc->user = usrdatum->value; @@ -2088,8 +2082,7 @@ static int convert_context(struct context *oldc, struct context *newc, void *p, /* Convert the type. */ typdatum = symtab_search(&args->newp->p_types, - sym_name(args->oldp, - SYM_TYPES, oldc->type - 1)); + sym_name(args->oldp, SYM_TYPES, oldc->type - 1)); if (!typdatum) goto bad; newc->type = typdatum->value; @@ -2123,8 +2116,7 @@ static int convert_context(struct context *oldc, struct context *newc, void *p, /* Check the validity of the new context. */ if (!policydb_context_isvalid(args->newp, newc)) { rc = convert_context_handle_invalid_context(args->state, - args->oldp, - oldc); + args->oldp, oldc); if (rc) goto bad; } @@ -2333,21 +2325,21 @@ int security_load_policy(struct selinux_state *state, void *data, size_t len, goto err_free_isids; } + /* + * Convert the internal representations of contexts + * in the new SID table. + */ + convert_data = kmalloc(sizeof(*convert_data), GFP_KERNEL); if (!convert_data) { rc = -ENOMEM; goto err_free_isids; } - /* - * Convert the internal representations of contexts - * in the new SID table. - */ convert_data->args.state = state; convert_data->args.oldp = &oldpolicy->policydb; convert_data->args.newp = &newpolicy->policydb; - convert_data->sidtab_params.func = convert_context; convert_data->sidtab_params.args = &convert_data->args; convert_data->sidtab_params.target = newpolicy->sidtab; diff --git a/security/selinux/ss/services.h b/security/selinux/ss/services.h index 9555ad074303..c4301626487f 100644 --- a/security/selinux/ss/services.h +++ b/security/selinux/ss/services.h @@ -29,10 +29,19 @@ struct selinux_policy { u32 latest_granting; } __randomize_layout; -void services_compute_xperms_drivers(struct extended_perms *xperms, - struct avtab_node *node); +struct convert_context_args { + struct selinux_state *state; + struct policydb *oldp; + struct policydb *newp; +}; +void services_compute_xperms_drivers(struct extended_perms *xperms, + struct avtab_node *node); void services_compute_xperms_decision(struct extended_perms_decision *xpermd, - struct avtab_node *node); + struct avtab_node *node); + +int services_convert_context(struct convert_context_args *args, + struct context *oldc, struct context *newc, + gfp_t gfp_flags); #endif /* _SS_SERVICES_H_ */ diff --git a/security/selinux/ss/sidtab.c b/security/selinux/ss/sidtab.c index db5cce385bf8..38d25173aebd 100644 --- a/security/selinux/ss/sidtab.c +++ b/security/selinux/ss/sidtab.c @@ -18,6 +18,7 @@ #include "flask.h" #include "security.h" #include "sidtab.h" +#include "services.h" struct sidtab_str_cache { struct rcu_head rcu_member; @@ -292,7 +293,6 @@ int sidtab_context_to_sid(struct sidtab *s, struct context *context, } count = s->count; - convert = s->convert; /* bail out if we already reached max entries */ rc = -EOVERFLOW; @@ -316,25 +316,29 @@ int sidtab_context_to_sid(struct sidtab *s, struct context *context, * if we are building a new sidtab, we need to convert the context * and insert it there as well */ + convert = s->convert; if (convert) { + struct sidtab *target = convert->target; + rc = -ENOMEM; - dst_convert = sidtab_do_lookup(convert->target, count, 1); + dst_convert = sidtab_do_lookup(target, count, 1); if (!dst_convert) { context_destroy(&dst->context); goto out_unlock; } - rc = convert->func(context, &dst_convert->context, - convert->args, GFP_ATOMIC); + rc = services_convert_context(convert->args, + context, &dst_convert->context, + GFP_ATOMIC); if (rc) { context_destroy(&dst->context); goto out_unlock; } dst_convert->sid = index_to_sid(count); dst_convert->hash = context_compute_hash(&dst_convert->context); - convert->target->count = count + 1; + target->count = count + 1; - hash_add_rcu(convert->target->context_to_sid, + hash_add_rcu(target->context_to_sid, &dst_convert->list, dst_convert->hash); } @@ -402,9 +406,10 @@ static int sidtab_convert_tree(union sidtab_entry_inner *edst, } i = 0; while (i < SIDTAB_LEAF_ENTRIES && *pos < count) { - rc = convert->func(&esrc->ptr_leaf->entries[i].context, - &edst->ptr_leaf->entries[i].context, - convert->args, GFP_KERNEL); + rc = services_convert_context(convert->args, + &esrc->ptr_leaf->entries[i].context, + &edst->ptr_leaf->entries[i].context, + GFP_KERNEL); if (rc) return rc; (*pos)++; diff --git a/security/selinux/ss/sidtab.h b/security/selinux/ss/sidtab.h index 9fce0d553fe2..72810a080e77 100644 --- a/security/selinux/ss/sidtab.h +++ b/security/selinux/ss/sidtab.h @@ -65,8 +65,7 @@ struct sidtab_isid_entry { }; struct sidtab_convert_params { - int (*func)(struct context *oldc, struct context *newc, void *args, gfp_t gfp_flags); - void *args; + struct convert_context_args *args; struct sidtab *target; }; diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index cadef2f6a75e..9a82a15685d1 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -4074,12 +4074,12 @@ static int smack_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) * returns zero on success, an error code otherwise */ static int smack_socket_getpeersec_stream(struct socket *sock, - char __user *optval, - int __user *optlen, unsigned len) + sockptr_t optval, sockptr_t optlen, + unsigned int len) { struct socket_smack *ssp; char *rcp = ""; - int slen = 1; + u32 slen = 1; int rc = 0; ssp = sock->sk->sk_security; @@ -4087,15 +4087,16 @@ static int smack_socket_getpeersec_stream(struct socket *sock, rcp = ssp->smk_packet->smk_known; slen = strlen(rcp) + 1; } - - if (slen > len) + if (slen > len) { rc = -ERANGE; - else if (copy_to_user(optval, rcp, slen) != 0) - rc = -EFAULT; + goto out_len; + } - if (put_user(slen, optlen) != 0) + if (copy_to_sockptr(optval, rcp, slen)) + rc = -EFAULT; +out_len: + if (copy_to_sockptr(optlen, &slen, sizeof(slen))) rc = -EFAULT; - return rc; } diff --git a/security/tomoyo/tomoyo.c b/security/tomoyo/tomoyo.c index 71e82d855ebf..af04a7b7eb28 100644 --- a/security/tomoyo/tomoyo.c +++ b/security/tomoyo/tomoyo.c @@ -135,6 +135,18 @@ static int tomoyo_path_truncate(const struct path *path) } /** + * tomoyo_file_truncate - Target for security_file_truncate(). + * + * @file: Pointer to "struct file". + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_file_truncate(struct file *file) +{ + return tomoyo_path_truncate(&file->f_path); +} + +/** * tomoyo_path_unlink - Target for security_path_unlink(). * * @parent: Pointer to "struct path". @@ -545,6 +557,7 @@ static struct security_hook_list tomoyo_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(bprm_check_security, tomoyo_bprm_check_security), LSM_HOOK_INIT(file_fcntl, tomoyo_file_fcntl), LSM_HOOK_INIT(file_open, tomoyo_file_open), + LSM_HOOK_INIT(file_truncate, tomoyo_file_truncate), LSM_HOOK_INIT(path_truncate, tomoyo_path_truncate), LSM_HOOK_INIT(path_unlink, tomoyo_path_unlink), LSM_HOOK_INIT(path_mkdir, tomoyo_path_mkdir), |