From c73275cf6834787ca090317f1d20dbfa3b7f05aa Mon Sep 17 00:00:00 2001 From: Gaosheng Cui Date: Tue, 23 Aug 2022 09:15:03 +0800 Subject: [PATCH 01/64] apparmor: fix a memleak in multi_transaction_new() In multi_transaction_new(), the variable t is not freed or passed out on the failure of copy_from_user(t->data, buf, size), which could lead to a memleak. Fix this bug by adding a put_multi_transaction(t) in the error path. Fixes: 1dea3b41e84c5 ("apparmor: speed up transactional queries") Signed-off-by: Gaosheng Cui Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index d066ccc219e2..7160e7aa58b9 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -868,8 +868,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; } From 9c4557efc558a68e4cd973490fd936d6e3414db8 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 6 Sep 2022 03:39:55 -0700 Subject: [PATCH 02/64] apparmor: fix lockdep warning when removing a namespace Fix the following lockdep warning [ 1119.158984] ============================================ [ 1119.158988] WARNING: possible recursive locking detected [ 1119.158996] 6.0.0-rc1+ #257 Tainted: G E N [ 1119.158999] -------------------------------------------- [ 1119.159001] bash/80100 is trying to acquire lock: [ 1119.159007] ffff88803e79b4a0 (&ns->lock/1){+.+.}-{4:4}, at: destroy_ns.part.0+0x43/0x140 [ 1119.159028] but task is already holding lock: [ 1119.159030] ffff8881009764a0 (&ns->lock/1){+.+.}-{4:4}, at: aa_remove_profiles+0x3f0/0x640 [ 1119.159040] other info that might help us debug this: [ 1119.159042] Possible unsafe locking scenario: [ 1119.159043] CPU0 [ 1119.159045] ---- [ 1119.159047] lock(&ns->lock/1); [ 1119.159051] lock(&ns->lock/1); [ 1119.159055] *** DEADLOCK *** Which is caused by an incorrect lockdep nesting notation Fixes: feb3c766a3ab ("apparmor: fix possible recursive lock warning in __aa_create_ns") Signed-off-by: John Johansen --- security/apparmor/policy.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 499c0209b6a4..fbdfcef91c61 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -1170,7 +1170,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); From f47acc4b7c43d566bf42816335830c4c17f9c200 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 6 Sep 2022 14:03:44 -0700 Subject: [PATCH 03/64] apparmor: reserve mediation classes Reserve mediation classes that exist in out of tree development branches or are used by userspace mediation helpers. Signed-off-by: John Johansen --- security/apparmor/include/apparmor.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h index 9c3fc36a0702..dd2c131ed170 100644 --- a/security/apparmor/include/apparmor.h +++ b/security/apparmor/include/apparmor.h @@ -28,8 +28,15 @@ #define AA_CLASS_SIGNAL 10 #define AA_CLASS_NET 14 #define AA_CLASS_LABEL 16 +#define AA_CLASS_POSIX_MQUEUE 17 +#define AA_CLASS_IO_URING 18 +#define AA_CLASS_MODULE 19 +#define AA_CLASS_DISPLAY_LSM 20 -#define AA_CLASS_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; From f4d6b94b40c966ddd9eeb0d451e8a02c595ec7e3 Mon Sep 17 00:00:00 2001 From: Jon Tourville Date: Mon, 11 Jul 2022 11:36:08 -0500 Subject: [PATCH 04/64] apparmor: use zstd compression for profile data Change the algorithm used by apparmor to compress profile data from zlib to zstd, using the new zstd API introduced in 5.16. Zstd provides a larger range of compression levels than zlib and significantly better performance at the default level (for a relatively small increase in compressed size). The apparmor module parameter raw_data_compression_level is now clamped to the minimum and maximum compression levels reported by the zstd library. A compression level of 0 retains the previous behavior of disabling policy compression instead of using zstd's behavior, which is to use the default compression level. Signed-off-by: Jon Tourville Signed-off-by: John Johansen --- security/apparmor/Kconfig | 4 +- security/apparmor/apparmorfs.c | 58 +++++++--------- security/apparmor/lsm.c | 10 +-- security/apparmor/policy_unpack.c | 111 ++++++++++++++---------------- 4 files changed, 81 insertions(+), 102 deletions(-) diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig index cb3496e00d8a..acac3bb3eef2 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/apparmorfs.c b/security/apparmor/apparmorfs.c index 7160e7aa58b9..d98bbf267fc7 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -21,7 +21,7 @@ #include #include #include -#include +#include #include #include @@ -1297,42 +1297,30 @@ 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; + if (aa_g_rawdata_compression_level == 0) { + const size_t wksp_len = zstd_dctx_workspace_bound(); + zstd_dctx *ctx; + void *wksp; + size_t out_len; + int ret = 0; - 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; + 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; + 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 @@ -1381,9 +1369,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; diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index e29cade7b662..ec873ff0a4bb 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -21,7 +21,7 @@ #include #include #include -#include +#include #include #include @@ -1361,7 +1361,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 = ZSTD_CLEVEL_DEFAULT; module_param_named(rawdata_compression_level, aa_g_rawdata_compression_level, aacompressionlevel, 0400); @@ -1543,9 +1543,9 @@ 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", + zstd_min_clevel(), + zstd_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/policy_unpack.c b/security/apparmor/policy_unpack.c index 55d31bac4f35..10e462d00321 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include "include/apparmor.h" #include "include/audit.h" @@ -1059,81 +1059,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); + 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; - 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; + 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; - - error = zlib_deflate(&strm, Z_FINISH); - if (error != Z_STREAM_END) { - error = -EINVAL; - goto fail_deflate; + ctx = zstd_init_cctx(wksp, wksp_len); + if (!ctx) { + 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); + out_len = zstd_compress_cctx(ctx, out, out_len, src, slen, ¶ms); + if (zstd_is_error(out_len)) { + ret = -EINVAL; + goto cleanup; + } + + 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); - - if (!dstbuf) { - error = -ENOMEM; - goto fail_deflate; + *dst = krealloc(out, out_len, GFP_KERNEL); } - *dst = dstbuf; - *dlen = strm.total_out; + if (!*dst) { + ret = -ENOMEM; + goto cleanup; + } -fail_stg_alloc: - zlib_deflateEnd(&strm); -fail_deflate_init: - kvfree(strm.workspace); - return error; + *dlen = out_len; -fail_deflate: - kvfree(stgbuf); - goto fail_stg_alloc; +cleanup: + if (ret) { + kvfree(out); + *dst = NULL; + } + + kvfree(wksp); + return ret; #else *dlen = slen; return 0; @@ -1142,7 +1134,6 @@ fail_deflate: static int compress_loaddata(struct aa_loaddata *data) { - AA_BUG(data->compressed_size > 0); /* @@ -1151,8 +1142,8 @@ 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); + int error = compress_zstd(udata, data->size, &data->data, + &data->compressed_size); if (error) return error; From 2218d08123362c63bab257caf5ec3bc1a6e87ae9 Mon Sep 17 00:00:00 2001 From: Jon Tourville Date: Mon, 11 Jul 2022 11:36:09 -0500 Subject: [PATCH 05/64] apparmor: expose compression level limits in sysfs Create two new files in apparmor's sysfs: /sys/kernel/security/apparmor/raw_data_compression_level_min /sys/kernel/security/apparmor/raw_data_compression_level_max These correspond to the minimum and maximum zstd compression levels that can be assigned to the apparmor module parameter raw_data_compression_level. Signed-off-by: Jon Tourville Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index d98bbf267fc7..044affb1ce83 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -1199,10 +1199,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", zstd_min_clevel()); + return 0; +} + +static int seq_ns_compress_max_show(struct seq_file *seq, void *v) +{ + seq_printf(seq, "%d\n", zstd_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 */ @@ -2382,6 +2396,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), { } }; From 408d53e923bd852d5d80243a642004163db53a87 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 30 Mar 2020 16:43:29 -0400 Subject: [PATCH 06/64] apparmor: compute file permissions on profile load Rather than computing file permissions for each file access, file permissions can be computed once on profile load and stored for lookup. Signed-off-by: Mike Salvatore Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 2 +- security/apparmor/domain.c | 10 +-- security/apparmor/file.c | 132 ++++++++++++++++++++---------- security/apparmor/include/file.h | 15 +++- security/apparmor/policy_unpack.c | 3 + 5 files changed, 112 insertions(+), 50 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 044affb1ce83..825b3093dcdd 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -624,7 +624,7 @@ static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms, if (state) { struct path_cond cond = { }; - tmp = aa_compute_fperms(dfa, state, &cond); + tmp = *(aa_lookup_fperms(&(profile->file), state, &cond)); } } else if (profile->policy.dfa) { if (!PROFILE_MEDIATES(profile, *match_str)) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 91689d34d281..2c99edd8953a 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -162,7 +162,7 @@ next: if (!state) goto fail; } - *perms = aa_compute_fperms(profile->file.dfa, state, &cond); + *perms = *(aa_lookup_fperms(&(profile->file), state, &cond)); aa_apply_modes_to_perms(profile, perms); if ((perms->allow & request) != request) return -EACCES; @@ -215,7 +215,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(&(profile->file), state, &cond)); aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum(perms, &tmp); label_for_each_cont(i, label, tp) { @@ -224,7 +224,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(&(profile->file), state, &cond)); aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum(perms, &tmp); } @@ -661,7 +661,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(&(profile->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, @@ -756,7 +756,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(&(profile->file), state, xname, cond, &perms); if (!(perms.allow & AA_MAY_ONEXEC)) { info = "no change_onexec valid for executable"; goto audit; diff --git a/security/apparmor/file.c b/security/apparmor/file.c index e1b7e93602e4..710b7d7517eb 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -201,49 +201,99 @@ static u32 map_old_perms(u32 old) return new; } -/** - * aa_compute_fperms - convert dfa compressed perms to internal perms - * @dfa: dfa to compute 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. - * - * Returns: computed permission set - */ -struct aa_perms aa_compute_fperms(struct aa_dfa *dfa, unsigned int state, - struct path_cond *cond) +static void __aa_compute_fperms_allow(struct aa_perms *perms, + struct aa_dfa *dfa, + unsigned int state) { - /* 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 = { }; - - 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; + 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; + perms->allow |= AA_MAY_CHANGE_PROFILE; if (ACCEPT_TABLE(dfa)[state] & 0x40000000) - perms.allow |= AA_MAY_ONEXEC; + perms->allow |= AA_MAY_ONEXEC; +} + +static struct aa_perms __aa_compute_fperms_user(struct aa_dfa *dfa, + unsigned int 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); + + __aa_compute_fperms_allow(&perms, dfa, state); return perms; } +static struct aa_perms __aa_compute_fperms_other(struct aa_dfa *dfa, + unsigned int 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); + + __aa_compute_fperms_allow(&perms, dfa, state); + + return perms; +} + +/** + * aa_compute_fperms - convert dfa compressed perms to internal perms and store + * them so they can be retrieved later. + * @file_rules: a file_rules structure containing a dfa (NOT NULL) for which + * permissions will be computed (NOT NULL) + * + * TODO: convert from dfa + state to permission entry + */ +void aa_compute_fperms(struct aa_file_rules *file_rules) +{ + int state; + int state_count = file_rules->dfa->tables[YYTD_ID_BASE]->td_lolen; + + // DFAs are restricted from having a state_count of less than 2 + file_rules->fperms_table = kvzalloc( + state_count * 2 * sizeof(struct aa_perms), GFP_KERNEL); + + // Since fperms_table is initialized with zeroes via kvzalloc(), we can + // skip the trap state (state == 0) + for (state = 1; state < state_count; state++) { + file_rules->fperms_table[state * 2] = + __aa_compute_fperms_user(file_rules->dfa, state); + file_rules->fperms_table[state * 2 + 1] = + __aa_compute_fperms_other(file_rules->dfa, state); + } +} + +/** + * 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 + * + * Returns: a pointer to a file permission set + */ +struct aa_perms default_perms = {}; +struct aa_perms *aa_lookup_fperms(struct aa_file_rules *file_rules, + unsigned int state, struct path_cond *cond) +{ + if (!(file_rules->fperms_table)) + return &default_perms; + + if (uid_eq(current_fsuid(), cond->uid)) + return &(file_rules->fperms_table[state * 2]); + + return &(file_rules->fperms_table[state * 2 + 1]); +} + /** * aa_str_perms - find permission that match @name * @dfa: to match against (MAYBE NULL) @@ -254,13 +304,13 @@ 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, +unsigned int aa_str_perms(struct aa_file_rules *file_rules, unsigned int 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); + state = aa_dfa_match(file_rules->dfa, start, name); + *perms = *(aa_lookup_fperms(file_rules, state, cond)); return state; } @@ -273,7 +323,7 @@ int __aa_path_perm(const char *op, struct aa_profile *profile, const char *name, if (profile_unconfined(profile)) return 0; - aa_str_perms(profile->file.dfa, profile->file.start, name, cond, perms); + aa_str_perms(&(profile->file), profile->file.start, name, cond, perms); if (request & ~perms->allow) e = -EACCES; return aa_audit_file(profile, perms, op, request, name, NULL, NULL, @@ -380,7 +430,7 @@ 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(&(profile->file), profile->file.start, lname, cond, &lperms); if (!(lperms.allow & AA_MAY_LINK)) @@ -388,7 +438,7 @@ static int profile_path_link(struct aa_profile *profile, /* 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); + aa_str_perms(&(profile->file), state, tname, cond, &perms); /* force audit/quiet masks for link are stored in the second entry * in the link pair. @@ -410,7 +460,7 @@ 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, + aa_str_perms(&(profile->file), profile->file.start, tname, cond, &perms); /* AA_MAY_LINK is not considered in the subset test */ diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index 029cb20e322d..ab201d625a34 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -181,11 +181,13 @@ struct aa_file_rules { /* struct perms perms; */ struct aa_domain trans; /* TODO: add delegate table */ + struct aa_perms *fperms_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, +void aa_compute_fperms(struct aa_file_rules *file_rules); +struct aa_perms *aa_lookup_fperms(struct aa_file_rules *file_rules, + unsigned int state, struct path_cond *cond); +unsigned int aa_str_perms(struct aa_file_rules *file_rules, unsigned int start, const char *name, struct path_cond *cond, struct aa_perms *perms); @@ -204,10 +206,17 @@ 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_fperms_table(struct aa_perms *fperms_table) +{ + if (fperms_table) + kvfree(fperms_table); +} + static inline void aa_free_file_rules(struct aa_file_rules *rules) { aa_put_dfa(rules->dfa); aa_free_domain_entries(&rules->trans); + aa_free_fperms_table(rules->fperms_table); } /** diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 10e462d00321..54175bca4256 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -22,6 +22,7 @@ #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" @@ -878,6 +879,8 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) } else profile->file.dfa = aa_get_dfa(nulldfa); + aa_compute_fperms(&(profile->file)); + if (!unpack_trans_table(e, profile)) { info = "failed to unpack profile transition table"; goto fail; From b5b57993504f91785fa70e002e5e494fb549726e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Sun, 31 May 2020 10:52:06 -0400 Subject: [PATCH 07/64] apparmor: compute xmatch permissions on profile load Rather than computing xmatch permissions each time access is requested, these permissions can be computed once on profile load and stored for lookup. Signed-off-by: Mike Salvatore Signed-off-by: John Johansen --- security/apparmor/domain.c | 4 ++-- security/apparmor/include/policy.h | 2 ++ security/apparmor/policy.c | 1 + security/apparmor/policy_unpack.c | 22 +++++++++++++++++++++- 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 2c99edd8953a..22351b6d71e6 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -339,7 +339,7 @@ static int aa_xattrs_match(const struct linux_binprm *bprm, /* Check xattr value */ state = aa_dfa_match_len(profile->xmatch, state, value, size); - perm = dfa_user_allow(profile->xmatch, state); + perm = profile->xmatch_perms[state]; if (!(perm & MAY_EXEC)) { ret = -EINVAL; goto out; @@ -419,7 +419,7 @@ restart: state = aa_dfa_leftmatch(profile->xmatch, DFA_START, name, &count); - perm = dfa_user_allow(profile->xmatch, state); + perm = profile->xmatch_perms[state]; /* any accepting state means a valid match. */ if (perm & MAY_EXEC) { int ret = 0; diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index 639b5b248e63..128c6a9430d4 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -104,6 +104,7 @@ struct aa_data { * @attach: human readable attachment string * @xmatch: optional extended matching for unconfined executables names * @xmatch_len: xmatch prefix len, used to determine xmatch priority + * @xmatch_perms: precomputed permissions for the xmatch DFA indexed by state * @audit: the auditing mode of the profile * @mode: the enforcement mode of the profile * @path_flags: flags controlling path generation behavior @@ -140,6 +141,7 @@ struct aa_profile { const char *attach; struct aa_dfa *xmatch; unsigned int xmatch_len; + u32 *xmatch_perms; enum audit_mode audit; long mode; u32 path_flags; diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index fbdfcef91c61..e2d23cd85cd2 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -231,6 +231,7 @@ void aa_free_profile(struct aa_profile *profile) kfree_sensitive(profile->secmark); kfree_sensitive(profile->dirname); aa_put_dfa(profile->xmatch); + kvfree(profile->xmatch_perms); aa_put_dfa(profile->policy.dfa); if (profile->data) { diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 54175bca4256..70b7a35b5b96 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -669,6 +669,23 @@ static int datacmp(struct rhashtable_compare_arg *arg, const void *obj) return strcmp(data->key, *key); } +static u32 *aa_compute_xmatch_perms(struct aa_dfa *xmatch) +{ + u32 *perms_table; + int state; + int state_count = xmatch->tables[YYTD_ID_BASE]->td_lolen; + + // DFAs are restricted from having a state_count of less than 2 + perms_table = kvcalloc(state_count, sizeof(u32), GFP_KERNEL); + + // Since perms_table is initialized with zeroes via kvcalloc(), we can + // skip the trap state (state == 0) + for (state = 1; state < state_count; state++) + perms_table[state] = dfa_user_allow(xmatch, state); + + return perms_table; +} + /** * unpack_profile - unpack a serialized profile * @e: serialized data extent information (NOT NULL) @@ -727,13 +744,16 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) info = "bad xmatch"; goto fail; } - /* xmatch_len is not optional if xmatch is set */ + /* neither xmatch_len not xmatch_perms are optional if xmatch is set */ if (profile->xmatch) { if (!unpack_u32(e, &tmp, NULL)) { info = "missing xmatch len"; goto fail; } profile->xmatch_len = tmp; + + profile->xmatch_perms = aa_compute_xmatch_perms( + profile->xmatch); } /* disconnected attachment string is optional */ From 754f209b811ac462e00ed0f79b48047c446f5c43 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 12 Nov 2020 10:07:25 -0800 Subject: [PATCH 08/64] apparmor: move fperm computation into policy_unpack fperm computation is only needed during policy_unpack so move the code there to isolate it fromt the run time code. Signed-off-by: John Johansen --- security/apparmor/file.c | 97 ------------------------------ security/apparmor/include/file.h | 1 - security/apparmor/policy_unpack.c | 98 +++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 98 deletions(-) diff --git a/security/apparmor/file.c b/security/apparmor/file.c index 710b7d7517eb..1227ae839154 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -174,103 +174,6 @@ static int path_name(const char *op, struct aa_label *label, return 0; } -/** - * 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 __aa_compute_fperms_allow(struct aa_perms *perms, - struct aa_dfa *dfa, - unsigned int 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 __aa_compute_fperms_user(struct aa_dfa *dfa, - unsigned int 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); - - __aa_compute_fperms_allow(&perms, dfa, state); - - return perms; -} - -static struct aa_perms __aa_compute_fperms_other(struct aa_dfa *dfa, - unsigned int 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); - - __aa_compute_fperms_allow(&perms, dfa, state); - - return perms; -} - -/** - * aa_compute_fperms - convert dfa compressed perms to internal perms and store - * them so they can be retrieved later. - * @file_rules: a file_rules structure containing a dfa (NOT NULL) for which - * permissions will be computed (NOT NULL) - * - * TODO: convert from dfa + state to permission entry - */ -void aa_compute_fperms(struct aa_file_rules *file_rules) -{ - int state; - int state_count = file_rules->dfa->tables[YYTD_ID_BASE]->td_lolen; - - // DFAs are restricted from having a state_count of less than 2 - file_rules->fperms_table = kvzalloc( - state_count * 2 * sizeof(struct aa_perms), GFP_KERNEL); - - // Since fperms_table is initialized with zeroes via kvzalloc(), we can - // skip the trap state (state == 0) - for (state = 1; state < state_count; state++) { - file_rules->fperms_table[state * 2] = - __aa_compute_fperms_user(file_rules->dfa, state); - file_rules->fperms_table[state * 2 + 1] = - __aa_compute_fperms_other(file_rules->dfa, state); - } -} - /** * aa_lookup_fperms - convert dfa compressed perms to internal perms * @dfa: dfa to lookup perms for (NOT NULL) diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index ab201d625a34..1f9e54aa1adf 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -184,7 +184,6 @@ struct aa_file_rules { struct aa_perms *fperms_table; }; -void aa_compute_fperms(struct aa_file_rules *file_rules); struct aa_perms *aa_lookup_fperms(struct aa_file_rules *file_rules, unsigned int state, struct path_cond *cond); unsigned int aa_str_perms(struct aa_file_rules *file_rules, unsigned int start, diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 70b7a35b5b96..c22c6815ff4b 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -669,6 +669,104 @@ static int datacmp(struct rhashtable_compare_arg *arg, const void *obj) return strcmp(data->key, *key); } +/** + * 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 __aa_compute_fperms_allow(struct aa_perms *perms, + struct aa_dfa *dfa, + unsigned int 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 __aa_compute_fperms_user(struct aa_dfa *dfa, + unsigned int 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); + + __aa_compute_fperms_allow(&perms, dfa, state); + + return perms; +} + +static struct aa_perms __aa_compute_fperms_other(struct aa_dfa *dfa, + unsigned int 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); + + __aa_compute_fperms_allow(&perms, dfa, state); + + return perms; +} + +/** + * aa_compute_fperms - convert dfa compressed perms to internal perms and store + * them so they can be retrieved later. + * @file_rules: a file_rules structure containing a dfa (NOT NULL) for which + * permissions will be computed (NOT NULL) + * + * TODO: convert from dfa + state to permission entry + */ +static void aa_compute_fperms(struct aa_file_rules *file_rules) +{ + int state; + int state_count = file_rules->dfa->tables[YYTD_ID_BASE]->td_lolen; + + // DFAs are restricted from having a state_count of less than 2 + file_rules->fperms_table = kvzalloc( + state_count * 2 * sizeof(struct aa_perms), GFP_KERNEL); + + // Since fperms_table is initialized with zeroes via kvzalloc(), we can + // skip the trap state (state == 0) + for (state = 1; state < state_count; state++) { + file_rules->fperms_table[state * 2] = + __aa_compute_fperms_user(file_rules->dfa, state); + file_rules->fperms_table[state * 2 + 1] = + __aa_compute_fperms_other(file_rules->dfa, state); + } +} + static u32 *aa_compute_xmatch_perms(struct aa_dfa *xmatch) { u32 *perms_table; From 0310f093ba95e7640c886298de36560c123df5bd Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 12 Nov 2020 10:26:26 -0800 Subject: [PATCH 09/64] apparmor: rework and cleanup fperm computation shorten the name of some of the mapping functions which shortens line lengths. change the mapping so it returns the perm table instead of operating directly on the file struct. Handle potential memory allocation failure. Signed-off-by: John Johansen --- security/apparmor/policy_unpack.c | 70 +++++++++++++++++-------------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index c22c6815ff4b..0f9a88354d63 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -697,9 +697,8 @@ static u32 map_old_perms(u32 old) return new; } -static void __aa_compute_fperms_allow(struct aa_perms *perms, - struct aa_dfa *dfa, - unsigned int state) +static void compute_fperms_allow(struct aa_perms *perms, struct aa_dfa *dfa, + unsigned int state) { perms->allow |= AA_MAY_GETATTR; @@ -710,8 +709,8 @@ static void __aa_compute_fperms_allow(struct aa_perms *perms, perms->allow |= AA_MAY_ONEXEC; } -static struct aa_perms __aa_compute_fperms_user(struct aa_dfa *dfa, - unsigned int state) +static struct aa_perms compute_fperms_user(struct aa_dfa *dfa, + unsigned int state) { struct aa_perms perms = { }; @@ -720,13 +719,13 @@ static struct aa_perms __aa_compute_fperms_user(struct aa_dfa *dfa, perms.quiet = map_old_perms(dfa_user_quiet(dfa, state)); perms.xindex = dfa_user_xindex(dfa, state); - __aa_compute_fperms_allow(&perms, dfa, state); + compute_fperms_allow(&perms, dfa, state); return perms; } -static struct aa_perms __aa_compute_fperms_other(struct aa_dfa *dfa, - unsigned int state) +static struct aa_perms compute_fperms_other(struct aa_dfa *dfa, + unsigned int state) { struct aa_perms perms = { }; @@ -735,7 +734,7 @@ static struct aa_perms __aa_compute_fperms_other(struct aa_dfa *dfa, perms.quiet = map_old_perms(dfa_other_quiet(dfa, state)); perms.xindex = dfa_other_xindex(dfa, state); - __aa_compute_fperms_allow(&perms, dfa, state); + compute_fperms_allow(&perms, dfa, state); return perms; } @@ -743,41 +742,46 @@ static struct aa_perms __aa_compute_fperms_other(struct aa_dfa *dfa, /** * aa_compute_fperms - convert dfa compressed perms to internal perms and store * them so they can be retrieved later. - * @file_rules: a file_rules structure containing a dfa (NOT NULL) for which - * permissions will be computed (NOT NULL) + * @dfa: a dfa using fperms to remap to internal permissions * - * TODO: convert from dfa + state to permission entry + * Returns: remapped perm table */ -static void aa_compute_fperms(struct aa_file_rules *file_rules) +static struct aa_perms *compute_fperms(struct aa_dfa *dfa) { int state; - int state_count = file_rules->dfa->tables[YYTD_ID_BASE]->td_lolen; + int state_count; + struct aa_perms *table; - // DFAs are restricted from having a state_count of less than 2 - file_rules->fperms_table = kvzalloc( - state_count * 2 * sizeof(struct aa_perms), GFP_KERNEL); + AA_BUG(!dfa); - // Since fperms_table is initialized with zeroes via kvzalloc(), we can - // skip the trap state (state == 0) + state_count = dfa->tables[YYTD_ID_BASE]->td_lolen; + /* DFAs are restricted from having a state_count of less than 2 */ + table = kvzalloc(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++) { - file_rules->fperms_table[state * 2] = - __aa_compute_fperms_user(file_rules->dfa, state); - file_rules->fperms_table[state * 2 + 1] = - __aa_compute_fperms_other(file_rules->dfa, state); + table[state * 2] = compute_fperms_user(dfa, state); + table[state * 2 + 1] = compute_fperms_other(dfa, state); } + + return table; } -static u32 *aa_compute_xmatch_perms(struct aa_dfa *xmatch) +static u32 *compute_xmatch_perms(struct aa_dfa *xmatch) { u32 *perms_table; int state; - int state_count = xmatch->tables[YYTD_ID_BASE]->td_lolen; + int state_count; - // DFAs are restricted from having a state_count of less than 2 + 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_table = kvcalloc(state_count, sizeof(u32), GFP_KERNEL); - // Since perms_table is initialized with zeroes via kvcalloc(), we can - // skip the trap state (state == 0) + /* zero init so skip the trap state (state == 0) */ for (state = 1; state < state_count; state++) perms_table[state] = dfa_user_allow(xmatch, state); @@ -850,8 +854,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) } profile->xmatch_len = tmp; - profile->xmatch_perms = aa_compute_xmatch_perms( - profile->xmatch); + profile->xmatch_perms = compute_xmatch_perms(profile->xmatch); } /* disconnected attachment string is optional */ @@ -997,8 +1000,11 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) } else profile->file.dfa = aa_get_dfa(nulldfa); - aa_compute_fperms(&(profile->file)); - + profile->file.fperms_table = compute_fperms(profile->file.dfa); + if (!profile->file.fperms_table) { + info = "failed to remap file permission table"; + goto fail; + } if (!unpack_trans_table(e, profile)) { info = "failed to unpack profile transition table"; goto fail; From e48ffd24c1d87dba227225615790cd059a707adb Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 13 Nov 2020 16:30:47 -0800 Subject: [PATCH 10/64] apparmor: convert xmatch to use aa_perms structure Convert xmatch from using perms encoded in the accept entry of the dfa to the common external aa_perms in a table. Signed-off-by: John Johansen --- security/apparmor/domain.c | 4 ++-- security/apparmor/include/policy.h | 3 ++- security/apparmor/policy_unpack.c | 13 +++++++++---- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 22351b6d71e6..4fcdcc0de48c 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -339,7 +339,7 @@ static int aa_xattrs_match(const struct linux_binprm *bprm, /* Check xattr value */ state = aa_dfa_match_len(profile->xmatch, state, value, size); - perm = profile->xmatch_perms[state]; + perm = profile->xmatch_perms[state].allow; if (!(perm & MAY_EXEC)) { ret = -EINVAL; goto out; @@ -419,7 +419,7 @@ restart: state = aa_dfa_leftmatch(profile->xmatch, DFA_START, name, &count); - perm = profile->xmatch_perms[state]; + perm = profile->xmatch_perms[state].allow; /* any accepting state means a valid match. */ if (perm & MAY_EXEC) { int ret = 0; diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index 128c6a9430d4..7882d5e5096b 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -141,7 +141,8 @@ struct aa_profile { const char *attach; struct aa_dfa *xmatch; unsigned int xmatch_len; - u32 *xmatch_perms; + struct aa_perms *xmatch_perms; + enum audit_mode audit; long mode; u32 path_flags; diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 0f9a88354d63..44910c201c49 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -769,9 +769,9 @@ static struct aa_perms *compute_fperms(struct aa_dfa *dfa) return table; } -static u32 *compute_xmatch_perms(struct aa_dfa *xmatch) +static struct aa_perms *compute_xmatch_perms(struct aa_dfa *xmatch) { - u32 *perms_table; + struct aa_perms *perms_table; int state; int state_count; @@ -779,11 +779,12 @@ static u32 *compute_xmatch_perms(struct aa_dfa *xmatch) state_count = xmatch->tables[YYTD_ID_BASE]->td_lolen; /* DFAs are restricted from having a state_count of less than 2 */ - perms_table = kvcalloc(state_count, sizeof(u32), GFP_KERNEL); + perms_table = 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_table[state] = dfa_user_allow(xmatch, state); + perms_table[state].allow = dfa_user_allow(xmatch, state); return perms_table; } @@ -855,6 +856,10 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) profile->xmatch_len = tmp; profile->xmatch_perms = compute_xmatch_perms(profile->xmatch); + if (!profile->xmatch_perms) { + info = "failed to convert xmatch permission table"; + goto fail; + } } /* disconnected attachment string is optional */ From e2967ede22978f132cd52929edff96c701bde0eb Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 17 Nov 2020 01:38:16 -0800 Subject: [PATCH 11/64] apparmor: compute policydb permission on profile load Rather than computing policydb permissions for each access permissions can be computed once on profile load and stored for lookup. Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 2 +- security/apparmor/include/perms.h | 13 +++++-- security/apparmor/include/policy.h | 1 + security/apparmor/label.c | 6 ++-- security/apparmor/lib.c | 42 ----------------------- security/apparmor/mount.c | 53 ++++++++++------------------ security/apparmor/net.c | 2 +- security/apparmor/policy.c | 2 +- security/apparmor/policy_unpack.c | 55 +++++++++++++++++++++++++++++- 9 files changed, 90 insertions(+), 86 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 825b3093dcdd..117783779337 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -633,7 +633,7 @@ static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms, state = aa_dfa_match_len(dfa, profile->policy.start[0], match_str, match_len); if (state) - aa_compute_perms(dfa, state, &tmp); + tmp = *aa_lookup_perms(profile->policy.perms, state); } aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum_raw(perms, &tmp); diff --git a/security/apparmor/include/perms.h b/security/apparmor/include/perms.h index 13f20c598448..de9631edb1ff 100644 --- a/security/apparmor/include/perms.h +++ b/security/apparmor/include/perms.h @@ -133,6 +133,17 @@ extern struct aa_perms allperms; xcheck(fn_for_each((L1), (P), (FN1)), fn_for_each((L2), (P), (FN2))) +extern struct aa_perms default_perms; + +static inline struct aa_perms *aa_lookup_perms(struct aa_perms *perms, + unsigned int state) +{ + if (!(perms)) + return &default_perms; + + return &(perms[state]); +} + 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,8 +152,6 @@ 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, diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index 7882d5e5096b..0dec18cd95e5 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -77,6 +77,7 @@ enum profile_mode { struct aa_policydb { /* Generic policy DFA specific rule types will be subsections of it */ struct aa_dfa *dfa; + struct aa_perms *perms; unsigned int start[AA_CLASS_LAST + 1]; }; diff --git a/security/apparmor/label.c b/security/apparmor/label.c index 0f36ee907438..ddb04417bdab 100644 --- a/security/apparmor/label.c +++ b/security/apparmor/label.c @@ -1328,7 +1328,7 @@ next: if (!state) goto fail; } - aa_compute_perms(profile->policy.dfa, state, perms); + *perms = *aa_lookup_perms(profile->policy.perms, state); aa_apply_modes_to_perms(profile, perms); if ((perms->allow & request) != request) return -EACCES; @@ -1379,7 +1379,7 @@ static int label_components_match(struct aa_profile *profile, return 0; next: - aa_compute_perms(profile->policy.dfa, state, &tmp); + tmp = *aa_lookup_perms(profile->policy.perms, state); aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum(perms, &tmp); label_for_each_cont(i, label, tp) { @@ -1388,7 +1388,7 @@ next: state = match_component(profile, tp, start); if (!state) goto fail; - aa_compute_perms(profile->policy.dfa, state, &tmp); + tmp = *aa_lookup_perms(profile->policy.perms, state); aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum(perms, &tmp); } diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index 1c72a61108d3..505ef5848f7c 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -315,48 +315,6 @@ void aa_apply_modes_to_perms(struct aa_profile *profile, struct aa_perms *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); -} - -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 diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c index f61247241803..1e978c2b1ee4 100644 --- a/security/apparmor/mount.c +++ b/security/apparmor/mount.c @@ -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, unsigned int 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_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->perms, 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->perms, state); if (perms->allow & AA_MAY_MOUNT) return 0; } @@ -341,7 +324,7 @@ static int match_mnt_path_str(struct aa_profile *profile, } error = -EACCES; - pos = do_match_mnt(profile->policy.dfa, + pos = do_match_mnt(&profile->policy, profile->policy.start[AA_CLASS_MOUNT], mntpnt, devname, type, flags, data, binary, &perms); if (pos) { @@ -601,7 +584,7 @@ static int profile_umount(struct aa_profile *profile, const struct path *path, state = aa_dfa_match(profile->policy.dfa, profile->policy.start[AA_CLASS_MOUNT], name); - perms = compute_mnt_perms(profile->policy.dfa, state); + perms = *aa_lookup_perms(profile->policy.perms, state); if (AA_MAY_UMOUNT & ~perms.allow) error = -EACCES; @@ -672,7 +655,7 @@ static struct aa_label *build_pivotroot(struct aa_profile *profile, 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); + perms = *aa_lookup_perms(profile->policy.perms, state); if (AA_MAY_PIVOTROOT & perms.allow) error = 0; diff --git a/security/apparmor/net.c b/security/apparmor/net.c index 7efe4d17273d..88e8a7ea54c0 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -125,7 +125,7 @@ int aa_profile_af_perm(struct aa_profile *profile, struct common_audit_data *sa, buffer[1] = cpu_to_be16((u16) type); state = aa_dfa_match_len(profile->policy.dfa, state, (char *) &buffer, 4); - aa_compute_perms(profile->policy.dfa, state, &perms); + perms = *aa_lookup_perms(profile->policy.perms, state); aa_apply_modes_to_perms(profile, &perms); return aa_check_perms(profile, &perms, request, sa, audit_net_cb); diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index e2d23cd85cd2..6c3086e2c820 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -233,7 +233,7 @@ void aa_free_profile(struct aa_profile *profile) aa_put_dfa(profile->xmatch); kvfree(profile->xmatch_perms); aa_put_dfa(profile->policy.dfa); - + kvfree(profile->policy.perms); if (profile->data) { rht = profile->data; profile->data = NULL; diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 44910c201c49..ed063385a83b 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -756,7 +756,7 @@ static struct aa_perms *compute_fperms(struct aa_dfa *dfa) state_count = dfa->tables[YYTD_ID_BASE]->td_lolen; /* DFAs are restricted from having a state_count of less than 2 */ - table = kvzalloc(state_count * 2 * sizeof(struct aa_perms), GFP_KERNEL); + table = kvcalloc(state_count * 2, sizeof(struct aa_perms), GFP_KERNEL); if (!table) return NULL; @@ -789,6 +789,54 @@ static struct aa_perms *compute_xmatch_perms(struct aa_dfa *xmatch) return perms_table; } +static u32 map_other(u32 x) +{ + return ((x & 0x3) << 8) | /* SETATTR/GETATTR */ + ((x & 0x1c) << 18) | /* ACCEPT/BIND/LISTEN */ + ((x & 0x60) << 19); /* SETOPT/GETOPT */ +} + +static struct aa_perms compute_perms_entry(struct aa_dfa *dfa, + unsigned int state) +{ + 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); + + /* for v5 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)); + + return perms; +} + +static struct aa_perms *compute_perms(struct aa_dfa *dfa) +{ + int state; + 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); + + return table; +} + /** * unpack_profile - unpack a serialized profile * @e: serialized data extent information (NOT NULL) @@ -986,6 +1034,11 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) goto fail; } else profile->policy.dfa = aa_get_dfa(nulldfa); + profile->policy.perms = compute_perms(profile->policy.dfa); + if (!profile->policy.perms) { + info = "failed to remap policydb permission table"; + goto fail; + } /* get file rules */ profile->file.dfa = unpack_dfa(e); From 53bdc46f4bdd20d477afb374767cabe627fd04ae Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 19 Nov 2020 10:37:48 -0800 Subject: [PATCH 12/64] apparmor: combine file_rules and aa_policydb into a single shared struct file_rules and policydb are almost the same and will need the same features in the future so combine them. Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 3 ++- security/apparmor/domain.c | 7 +++--- security/apparmor/file.c | 20 ++++++++------- security/apparmor/include/file.h | 39 +++--------------------------- security/apparmor/include/policy.h | 14 ++++++++--- security/apparmor/policy.c | 5 ++-- security/apparmor/policy_unpack.c | 11 +++++---- 7 files changed, 40 insertions(+), 59 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 117783779337..1625fee17fc7 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -619,7 +619,8 @@ static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms, return; if (profile->file.dfa && *match_str == AA_CLASS_FILE) { dfa = profile->file.dfa; - state = aa_dfa_match_len(dfa, profile->file.start, + state = aa_dfa_match_len(dfa, + profile->file.start[AA_CLASS_FILE], match_str + 1, match_len - 1); if (state) { struct path_cond cond = { }; diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 4fcdcc0de48c..819b7828cbc4 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -627,7 +627,7 @@ static struct aa_label *profile_transition(struct aa_profile *profile, { struct aa_label *new = NULL; const char *info = NULL, *name = NULL, *target = NULL; - unsigned int state = profile->file.start; + unsigned int state = profile->file.start[AA_CLASS_FILE]; struct aa_perms perms = {}; bool nonewprivs = false; int error = 0; @@ -723,7 +723,7 @@ 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; + unsigned int state = profile->file.start[AA_CLASS_FILE]; struct aa_perms perms = {}; const char *xname = NULL, *info = "change_profile onexec"; int error = -EACCES; @@ -1267,7 +1267,8 @@ static int change_profile_perms_wrapper(const char *op, const char *name, if (!error) error = change_profile_perms(profile, target, stack, request, - profile->file.start, perms); + profile->file.start[AA_CLASS_FILE], + perms); if (error) error = aa_audit_file(profile, perms, op, request, name, NULL, target, GLOBAL_ROOT_UID, info, diff --git a/security/apparmor/file.c b/security/apparmor/file.c index 1227ae839154..d2be851be412 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -185,16 +185,16 @@ static int path_name(const char *op, struct aa_label *label, * Returns: a pointer to a file permission set */ struct aa_perms default_perms = {}; -struct aa_perms *aa_lookup_fperms(struct aa_file_rules *file_rules, +struct aa_perms *aa_lookup_fperms(struct aa_policydb *file_rules, unsigned int state, struct path_cond *cond) { - if (!(file_rules->fperms_table)) + if (!(file_rules->perms)) return &default_perms; if (uid_eq(current_fsuid(), cond->uid)) - return &(file_rules->fperms_table[state * 2]); + return &(file_rules->perms[state * 2]); - return &(file_rules->fperms_table[state * 2 + 1]); + return &(file_rules->perms[state * 2 + 1]); } /** @@ -207,7 +207,7 @@ struct aa_perms *aa_lookup_fperms(struct aa_file_rules *file_rules, * * Returns: the final state in @dfa when beginning @start and walking @name */ -unsigned int aa_str_perms(struct aa_file_rules *file_rules, unsigned int start, +unsigned int aa_str_perms(struct aa_policydb *file_rules, unsigned int start, const char *name, struct path_cond *cond, struct aa_perms *perms) { @@ -226,7 +226,8 @@ int __aa_path_perm(const char *op, struct aa_profile *profile, const char *name, if (profile_unconfined(profile)) return 0; - aa_str_perms(&(profile->file), profile->file.start, name, cond, perms); + aa_str_perms(&(profile->file), profile->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, @@ -333,7 +334,8 @@ 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), profile->file.start, lname, + state = aa_str_perms(&(profile->file), + profile->file.start[AA_CLASS_FILE], lname, cond, &lperms); if (!(lperms.allow & AA_MAY_LINK)) @@ -363,8 +365,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), profile->file.start, tname, cond, - &perms); + aa_str_perms(&(profile->file), profile->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/file.h b/security/apparmor/include/file.h index 1f9e54aa1adf..736b8f655404 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; @@ -164,29 +165,9 @@ int aa_audit_file(struct aa_profile *profile, struct aa_perms *perms, 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 *fperms_table; -}; - -struct aa_perms *aa_lookup_fperms(struct aa_file_rules *file_rules, - unsigned int state, struct path_cond *cond); -unsigned int aa_str_perms(struct aa_file_rules *file_rules, unsigned int start, +struct aa_perms *aa_lookup_fperms(struct aa_policydb *file_rules, + unsigned int state, struct path_cond *cond); +unsigned int aa_str_perms(struct aa_policydb *file_rules, unsigned int start, const char *name, struct path_cond *cond, struct aa_perms *perms); @@ -205,18 +186,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_fperms_table(struct aa_perms *fperms_table) -{ - if (fperms_table) - kvfree(fperms_table); -} - -static inline void aa_free_file_rules(struct aa_file_rules *rules) -{ - aa_put_dfa(rules->dfa); - aa_free_domain_entries(&rules->trans); - aa_free_fperms_table(rules->fperms_table); -} /** * aa_map_file_perms - map file flags to AppArmor permissions diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index 0dec18cd95e5..9bafeb3847d5 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -75,13 +75,21 @@ enum profile_mode { * 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; struct aa_perms *perms; + struct aa_domain trans; unsigned int 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_domain_entries(&policy->trans); + +} + /* struct aa_data - generic data structure * key: name for retrieving this data * size: size of data in bytes @@ -151,7 +159,7 @@ struct aa_profile { int size; struct aa_policydb policy; - struct aa_file_rules file; + struct aa_policydb file; struct aa_caps caps; int xattr_count; diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 6c3086e2c820..0814ee57a06b 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -219,7 +219,7 @@ void aa_free_profile(struct aa_profile *profile) aa_put_ns(profile->ns); kfree_sensitive(profile->rename); - aa_free_file_rules(&profile->file); + aa_destroy_policydb(&profile->file); aa_free_cap_rules(&profile->caps); aa_free_rlimit_rules(&profile->rlimits); @@ -232,8 +232,7 @@ void aa_free_profile(struct aa_profile *profile) kfree_sensitive(profile->dirname); aa_put_dfa(profile->xmatch); kvfree(profile->xmatch_perms); - aa_put_dfa(profile->policy.dfa); - kvfree(profile->policy.perms); + aa_destroy_policydb(&profile->policy); if (profile->data) { rht = profile->data; profile->data = NULL; diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index ed063385a83b..726fa02026b5 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -1048,18 +1048,19 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) info = "failed to unpack profile file rules"; goto fail; } else if (profile->file.dfa) { - if (!unpack_u32(e, &profile->file.start, "dfa_start")) + if (!unpack_u32(e, &profile->file.start[AA_CLASS_FILE], + "dfa_start")) /* default start state */ - profile->file.start = DFA_START; + profile->file.start[AA_CLASS_FILE] = 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]; + profile->file.start[AA_CLASS_FILE] = profile->policy.start[AA_CLASS_FILE]; } else profile->file.dfa = aa_get_dfa(nulldfa); - profile->file.fperms_table = compute_fperms(profile->file.dfa); - if (!profile->file.fperms_table) { + profile->file.perms = compute_fperms(profile->file.dfa); + if (!profile->file.perms) { info = "failed to remap file permission table"; goto fail; } From 048d49544455b3e3a535c4ec89057ea5ca8676f0 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sat, 21 Nov 2020 01:42:40 -0800 Subject: [PATCH 13/64] apparmor: convert xmatch to using the new shared policydb struct continue permission unification by converting xmatch to use the policydb struct that is used by the other profile dfas. Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 2 +- security/apparmor/domain.c | 22 ++++++++++++---------- security/apparmor/include/apparmor.h | 1 + security/apparmor/include/policy.h | 4 +--- security/apparmor/policy.c | 3 +-- security/apparmor/policy_unpack.c | 25 ++++++++++++------------- 6 files changed, 28 insertions(+), 29 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 1625fee17fc7..a2d12b80592b 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -1095,7 +1095,7 @@ static int seq_profile_attach_show(struct seq_file *seq, void *v) struct aa_profile *profile = labels_profile(label); if (profile->attach) seq_printf(seq, "%s\n", profile->attach); - else if (profile->xmatch) + else if (profile->xmatch.dfa) seq_puts(seq, "\n"); else seq_printf(seq, "%s\n", profile->base.name); diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 819b7828cbc4..0df17fb236c7 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -321,7 +321,7 @@ static int aa_xattrs_match(const struct linux_binprm *bprm, might_sleep(); /* transition from exec match to xattr set */ - state = aa_dfa_outofband_transition(profile->xmatch, state); + state = aa_dfa_outofband_transition(profile->xmatch.dfa, state); d = bprm->file->f_path.dentry; for (i = 0; i < profile->xattr_count; i++) { @@ -335,18 +335,19 @@ static int aa_xattrs_match(const struct linux_binprm *bprm, * that not present xattr can be distinguished from a 0 * length value or rule that matches any value */ - state = aa_dfa_null_transition(profile->xmatch, state); + state = aa_dfa_null_transition(profile->xmatch.dfa, + state); /* Check xattr value */ - state = aa_dfa_match_len(profile->xmatch, state, value, - size); - perm = profile->xmatch_perms[state].allow; + state = aa_dfa_match_len(profile->xmatch.dfa, state, + value, size); + perm = profile->xmatch.perms[state].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(profile->xmatch.dfa, state); if (size < 0) { /* * No xattr match, so verify if transition to @@ -413,13 +414,14 @@ restart: * as another profile, signal a conflict and refuse to * match. */ - if (profile->xmatch) { + if (profile->xmatch.dfa) { unsigned int state, count; u32 perm; - state = aa_dfa_leftmatch(profile->xmatch, DFA_START, - name, &count); - perm = profile->xmatch_perms[state].allow; + state = aa_dfa_leftmatch(profile->xmatch.dfa, + profile->xmatch.start[AA_CLASS_XMATCH], + name, &count); + perm = profile->xmatch.perms[state].allow; /* any accepting state means a valid match. */ if (perm & MAY_EXEC) { int ret = 0; diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h index dd2c131ed170..8fd66a4ca0b8 100644 --- a/security/apparmor/include/apparmor.h +++ b/security/apparmor/include/apparmor.h @@ -26,6 +26,7 @@ #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 diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index 9bafeb3847d5..44d8cbb1c368 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -113,7 +113,6 @@ struct aa_data { * @attach: human readable attachment string * @xmatch: optional extended matching for unconfined executables names * @xmatch_len: xmatch prefix len, used to determine xmatch priority - * @xmatch_perms: precomputed permissions for the xmatch DFA indexed by state * @audit: the auditing mode of the profile * @mode: the enforcement mode of the profile * @path_flags: flags controlling path generation behavior @@ -148,9 +147,8 @@ struct aa_profile { const char *rename; const char *attach; - struct aa_dfa *xmatch; + struct aa_policydb xmatch; unsigned int xmatch_len; - struct aa_perms *xmatch_perms; enum audit_mode audit; long mode; diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 0814ee57a06b..cdcf26c9bed5 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -230,8 +230,7 @@ void aa_free_profile(struct aa_profile *profile) kfree_sensitive(profile->secmark[i].label); kfree_sensitive(profile->secmark); kfree_sensitive(profile->dirname); - aa_put_dfa(profile->xmatch); - kvfree(profile->xmatch_perms); + aa_destroy_policydb(&profile->xmatch); aa_destroy_policydb(&profile->policy); if (profile->data) { rht = profile->data; diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 726fa02026b5..f2a075986e49 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -771,7 +771,7 @@ static struct aa_perms *compute_fperms(struct aa_dfa *dfa) static struct aa_perms *compute_xmatch_perms(struct aa_dfa *xmatch) { - struct aa_perms *perms_table; + struct aa_perms *perms; int state; int state_count; @@ -779,14 +779,13 @@ static struct aa_perms *compute_xmatch_perms(struct aa_dfa *xmatch) state_count = xmatch->tables[YYTD_ID_BASE]->td_lolen; /* DFAs are restricted from having a state_count of less than 2 */ - perms_table = kvcalloc(state_count, sizeof(struct aa_perms), - GFP_KERNEL); + 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_table[state].allow = dfa_user_allow(xmatch, state); + perms[state].allow = dfa_user_allow(xmatch, state); - return perms_table; + return perms; } static u32 map_other(u32 x) @@ -888,23 +887,23 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) (void) unpack_str(e, &profile->attach, "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; + profile->xmatch.dfa = unpack_dfa(e); + if (IS_ERR(profile->xmatch.dfa)) { + error = PTR_ERR(profile->xmatch.dfa); + profile->xmatch.dfa = NULL; info = "bad xmatch"; goto fail; } /* neither xmatch_len not xmatch_perms are optional if xmatch is set */ - if (profile->xmatch) { + if (profile->xmatch.dfa) { if (!unpack_u32(e, &tmp, NULL)) { info = "missing xmatch len"; goto fail; } profile->xmatch_len = tmp; - - profile->xmatch_perms = compute_xmatch_perms(profile->xmatch); - if (!profile->xmatch_perms) { + profile->xmatch.start[AA_CLASS_XMATCH] = DFA_START; + profile->xmatch.perms = compute_xmatch_perms(profile->xmatch.dfa); + if (!profile->xmatch.perms) { info = "failed to convert xmatch permission table"; goto fail; } From 7572fea31e3e5c4c19154ccc064eb1f83dfe1333 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 13 Nov 2020 01:46:23 -0800 Subject: [PATCH 14/64] apparmor: convert fperm lookup to use accept as an index Remap file dfa accept table from embedded perms to index and then move fperm lookup to use the accept entry as an index into the fperm table. This is a step toward unifying permission lookup. Signed-off-by: John Johansen --- security/apparmor/file.c | 6 ++-- security/apparmor/policy_unpack.c | 57 ++++++++++++++++++++++--------- 2 files changed, 44 insertions(+), 19 deletions(-) diff --git a/security/apparmor/file.c b/security/apparmor/file.c index d2be851be412..7bddec3df75f 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -188,13 +188,15 @@ struct aa_perms default_perms = {}; struct aa_perms *aa_lookup_fperms(struct aa_policydb *file_rules, unsigned int state, struct path_cond *cond) { + unsigned int index = ACCEPT_TABLE(file_rules->dfa)[state]; + if (!(file_rules->perms)) return &default_perms; if (uid_eq(current_fsuid(), cond->uid)) - return &(file_rules->perms[state * 2]); + return &(file_rules->perms[index]); - return &(file_rules->perms[state * 2 + 1]); + return &(file_rules->perms[index + 1]); } /** diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index f2a075986e49..4cf62c1be388 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -836,6 +836,29 @@ static struct aa_perms *compute_perms(struct aa_dfa *dfa) 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; +} + /** * unpack_profile - unpack a serialized profile * @e: serialized data extent information (NOT NULL) @@ -1051,6 +1074,16 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) "dfa_start")) /* default start state */ profile->file.start[AA_CLASS_FILE] = DFA_START; + profile->file.perms = compute_fperms(profile->file.dfa); + if (!profile->file.perms) { + info = "failed to remap file permission table"; + goto fail; + } + remap_dfa_accept(profile->file.dfa, 2); + if (!unpack_trans_table(e, profile)) { + info = "failed to unpack profile transition table"; + goto fail; + } } else if (profile->policy.dfa && profile->policy.start[AA_CLASS_FILE]) { profile->file.dfa = aa_get_dfa(profile->policy.dfa); @@ -1058,16 +1091,6 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) } else profile->file.dfa = aa_get_dfa(nulldfa); - profile->file.perms = compute_fperms(profile->file.dfa); - if (!profile->file.perms) { - info = "failed to remap file permission table"; - goto fail; - } - if (!unpack_trans_table(e, profile)) { - info = "failed to unpack profile transition table"; - goto fail; - } - if (unpack_nameX(e, AA_STRUCT, "data")) { info = "out of memory"; profile->data = kzalloc(sizeof(*profile->data), GFP_KERNEL); @@ -1198,9 +1221,7 @@ 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)) - return false; - if (!verify_xindex(dfa_other_xindex(dfa, i), table_size)) + if (!verify_xindex(ACCEPT_TABLE(dfa)[i], table_size)) return false; } return true; @@ -1211,14 +1232,16 @@ 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); + !verify_dfa_xindex(profile->file.dfa, + profile->file.trans.size)) { + audit_iface(profile, NULL, NULL, + "Unpack: Invalid named transition", NULL, -EPROTO); return -EPROTO; } From 2d63dd43ae334ec6f5374d37bb06c4cc57621b3c Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 13 Nov 2020 23:36:09 -0800 Subject: [PATCH 15/64] apparmor: convert xmatch lookup to use accept as an index Remap xmatch dfa accept table from embedded perms to an index and then move xmatch lookup to use accept entry to index into the xmatch table. This is step towards unifying permission lookup and reducing the size of permissions tables. Signed-off-by: John Johansen --- security/apparmor/domain.c | 10 ++++++---- security/apparmor/policy_unpack.c | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 0df17fb236c7..45a8887021f1 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -328,7 +328,7 @@ static int aa_xattrs_match(const struct linux_binprm *bprm, size = vfs_getxattr_alloc(&init_user_ns, d, profile->xattrs[i], &value, value_size, GFP_KERNEL); if (size >= 0) { - u32 perm; + u32 index, perm; /* * Check the xattr presence before value. This ensure @@ -340,7 +340,8 @@ static int aa_xattrs_match(const struct linux_binprm *bprm, /* Check xattr value */ state = aa_dfa_match_len(profile->xmatch.dfa, state, value, size); - perm = profile->xmatch.perms[state].allow; + index = ACCEPT_TABLE(profile->xmatch.dfa)[state]; + perm = profile->xmatch.perms[index].allow; if (!(perm & MAY_EXEC)) { ret = -EINVAL; goto out; @@ -416,12 +417,13 @@ restart: */ if (profile->xmatch.dfa) { unsigned int state, count; - u32 perm; + u32 index, perm; state = aa_dfa_leftmatch(profile->xmatch.dfa, profile->xmatch.start[AA_CLASS_XMATCH], name, &count); - perm = profile->xmatch.perms[state].allow; + index = ACCEPT_TABLE(profile->xmatch.dfa)[state]; + perm = profile->xmatch.perms[index].allow; /* any accepting state means a valid match. */ if (perm & MAY_EXEC) { int ret = 0; diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 4cf62c1be388..4cdc96988783 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -930,6 +930,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) info = "failed to convert xmatch permission table"; goto fail; } + remap_dfa_accept(profile->xmatch.dfa, 1); } /* disconnected attachment string is optional */ From bf690f59d0429c62de4db1234f16557eedcb39bf Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sat, 10 Apr 2021 02:09:44 -0700 Subject: [PATCH 16/64] apparmor: cleanup shared permission struct The shared permissions struct has the stop field which is unneeded and the "reserved" subtree field commented which is needed. Also reorganize so that the entries are logically grouped. Signed-off-by: John Johansen --- security/apparmor/include/perms.h | 17 +++++++---------- security/apparmor/lib.c | 4 ++-- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/security/apparmor/include/perms.h b/security/apparmor/include/perms.h index de9631edb1ff..1f3e7680e809 100644 --- a/security/apparmor/include/perms.h +++ b/security/apparmor/include/perms.h @@ -65,22 +65,19 @@ 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 * / - */ + u32 audit; /* set only when allow is set */ + u32 quiet; /* set only when ~allow | deny */ + u32 hide; /* set only when ~allow | deny */ + u16 xindex; }; diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index 505ef5848f7c..974a217218a6 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -327,11 +327,11 @@ void aa_perms_accum_raw(struct aa_perms *accum, struct aa_perms *addend) 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; + accum->subtree |= addend->subtree & ~addend->deny; } /** @@ -346,11 +346,11 @@ void aa_perms_accum(struct aa_perms *accum, struct aa_perms *addend) 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; + accum->subtree &= addend->subtree & ~accum->deny; } void aa_profile_match_label(struct aa_profile *profile, struct aa_label *label, From e844fe9b51c984472ea98be3b2d1201ba9ee3213 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sat, 16 Jul 2022 01:53:46 -0700 Subject: [PATCH 17/64] apparmor: convert policy lookup to use accept as an index Remap polidydb dfa accept table from embedded perms to an index, and then move the perm lookup to use the accept entry as an index into the perm table. This is done so that the perm table can be separated from the dfa, allowing dfa accept to index to share expanded permission sets. Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 2 +- security/apparmor/include/perms.h | 8 -------- security/apparmor/include/policy.h | 12 ++++++++++++ security/apparmor/label.c | 6 +++--- security/apparmor/mount.c | 8 ++++---- security/apparmor/net.c | 2 +- security/apparmor/policy_unpack.c | 19 ++++++++++++------- 7 files changed, 33 insertions(+), 24 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index a2d12b80592b..f2b78108bae8 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -634,7 +634,7 @@ static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms, state = aa_dfa_match_len(dfa, profile->policy.start[0], match_str, match_len); if (state) - tmp = *aa_lookup_perms(profile->policy.perms, state); + tmp = *aa_lookup_perms(&profile->policy, state); } aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum_raw(perms, &tmp); diff --git a/security/apparmor/include/perms.h b/security/apparmor/include/perms.h index 1f3e7680e809..1014a7bbc027 100644 --- a/security/apparmor/include/perms.h +++ b/security/apparmor/include/perms.h @@ -132,14 +132,6 @@ extern struct aa_perms allperms; extern struct aa_perms default_perms; -static inline struct aa_perms *aa_lookup_perms(struct aa_perms *perms, - unsigned int state) -{ - if (!(perms)) - return &default_perms; - - return &(perms[state]); -} void aa_perm_mask_to_str(char *str, size_t str_size, const char *chrs, u32 mask); diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index 44d8cbb1c368..31c0af876250 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -90,6 +90,18 @@ static inline void aa_destroy_policydb(struct aa_policydb *policy) } +static inline struct aa_perms *aa_lookup_perms(struct aa_policydb *policy, + unsigned int 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 diff --git a/security/apparmor/label.c b/security/apparmor/label.c index ddb04417bdab..30cb68641c0f 100644 --- a/security/apparmor/label.c +++ b/security/apparmor/label.c @@ -1328,7 +1328,7 @@ next: if (!state) goto fail; } - *perms = *aa_lookup_perms(profile->policy.perms, state); + *perms = *aa_lookup_perms(&profile->policy, state); aa_apply_modes_to_perms(profile, perms); if ((perms->allow & request) != request) return -EACCES; @@ -1379,7 +1379,7 @@ static int label_components_match(struct aa_profile *profile, return 0; next: - tmp = *aa_lookup_perms(profile->policy.perms, state); + tmp = *aa_lookup_perms(&profile->policy, state); aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum(perms, &tmp); label_for_each_cont(i, label, tp) { @@ -1388,7 +1388,7 @@ next: state = match_component(profile, tp, start); if (!state) goto fail; - tmp = *aa_lookup_perms(profile->policy.perms, state); + tmp = *aa_lookup_perms(&profile->policy, state); aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum(perms, &tmp); } diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c index 1e978c2b1ee4..7594f3a3441e 100644 --- a/security/apparmor/mount.c +++ b/security/apparmor/mount.c @@ -249,7 +249,7 @@ static int do_match_mnt(struct aa_policydb *policy, unsigned int start, state = match_mnt_flags(policy->dfa, state, flags); if (!state) return 4; - *perms = *aa_lookup_perms(policy->perms, state); + *perms = *aa_lookup_perms(policy, state); if (perms->allow & AA_MAY_MOUNT) return 0; @@ -262,7 +262,7 @@ static int do_match_mnt(struct aa_policydb *policy, unsigned int start, state = aa_dfa_match(policy->dfa, state, data); if (!state) return 5; - *perms = *aa_lookup_perms(policy->perms, state); + *perms = *aa_lookup_perms(policy, state); if (perms->allow & AA_MAY_MOUNT) return 0; } @@ -584,7 +584,7 @@ static int profile_umount(struct aa_profile *profile, const struct path *path, state = aa_dfa_match(profile->policy.dfa, profile->policy.start[AA_CLASS_MOUNT], name); - perms = *aa_lookup_perms(profile->policy.perms, state); + perms = *aa_lookup_perms(&profile->policy, state); if (AA_MAY_UMOUNT & ~perms.allow) error = -EACCES; @@ -655,7 +655,7 @@ static struct aa_label *build_pivotroot(struct aa_profile *profile, new_name); state = aa_dfa_null_transition(profile->policy.dfa, state); state = aa_dfa_match(profile->policy.dfa, state, old_name); - perms = *aa_lookup_perms(profile->policy.perms, state); + perms = *aa_lookup_perms(&profile->policy, state); if (AA_MAY_PIVOTROOT & perms.allow) error = 0; diff --git a/security/apparmor/net.c b/security/apparmor/net.c index 88e8a7ea54c0..fcfb97079e1b 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -125,7 +125,7 @@ int aa_profile_af_perm(struct aa_profile *profile, struct common_audit_data *sa, buffer[1] = cpu_to_be16((u16) type); state = aa_dfa_match_len(profile->policy.dfa, state, (char *) &buffer, 4); - perms = *aa_lookup_perms(profile->policy.perms, state); + perms = *aa_lookup_perms(&profile->policy, state); aa_apply_modes_to_perms(profile, &perms); return aa_check_perms(profile, &perms, request, sa, audit_net_cb); diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 4cdc96988783..0917412ba48f 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -1055,13 +1055,15 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) } if (!unpack_nameX(e, AA_STRUCTEND, NULL)) goto fail; + profile->policy.perms = compute_perms(profile->policy.dfa); + if (!profile->policy.perms) { + info = "failed to remap policydb permission table"; + goto fail; + } + /* Do not remap internal dfas */ + remap_dfa_accept(profile->policy.dfa, 1); } else profile->policy.dfa = aa_get_dfa(nulldfa); - profile->policy.perms = compute_perms(profile->policy.dfa); - if (!profile->policy.perms) { - info = "failed to remap policydb permission table"; - goto fail; - } /* get file rules */ profile->file.dfa = unpack_dfa(e); @@ -1238,9 +1240,12 @@ static bool verify_dfa_xindex(struct aa_dfa *dfa, int table_size) */ static int verify_profile(struct aa_profile *profile) { - if (profile->file.dfa && + if ((profile->file.dfa && !verify_dfa_xindex(profile->file.dfa, - profile->file.trans.size)) { + profile->file.trans.size)) || + (profile->policy.dfa && + !verify_dfa_xindex(profile->policy.dfa, + profile->policy.trans.size))) { audit_iface(profile, NULL, NULL, "Unpack: Invalid named transition", NULL, -EPROTO); return -EPROTO; From 33fc95d8293cfca352ac875668857293e22d7d51 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 17 Jan 2022 13:43:49 -0800 Subject: [PATCH 18/64] apparmor: preparse for state being more than just an integer Convert from an unsigned int to a state_t for state position. This is a step in prepping for the state position carrying some additional flags, and a limited form of backtracking to support variables. Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 2 +- security/apparmor/domain.c | 25 ++++++------- security/apparmor/file.c | 12 +++---- security/apparmor/include/file.h | 8 ++--- security/apparmor/include/label.h | 6 ++-- security/apparmor/include/lib.h | 4 +-- security/apparmor/include/match.h | 28 +++++++-------- security/apparmor/include/policy.h | 14 ++++---- security/apparmor/ipc.c | 2 +- security/apparmor/label.c | 14 ++++---- security/apparmor/lib.c | 2 +- security/apparmor/match.c | 58 +++++++++++++++--------------- security/apparmor/mount.c | 10 +++--- security/apparmor/net.c | 2 +- security/apparmor/policy_unpack.c | 16 ++++----- 15 files changed, 101 insertions(+), 102 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index f2b78108bae8..fb9d2ccb34d6 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -613,7 +613,7 @@ static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms, { struct aa_perms tmp = { }; struct aa_dfa *dfa; - unsigned int state = 0; + aa_state_t state = DFA_NOMATCH; if (profile_unconfined(profile)) return; diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 45a8887021f1..5883f0fc02d3 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -95,9 +95,9 @@ 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) { const char *ns_name; @@ -132,7 +132,7 @@ 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_profile *tp; @@ -192,14 +192,14 @@ 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_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) { @@ -252,7 +252,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 +286,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,7 +308,7 @@ 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; @@ -416,7 +416,8 @@ restart: * match. */ if (profile->xmatch.dfa) { - unsigned int state, count; + unsigned int count; + aa_state_t state; u32 index, perm; state = aa_dfa_leftmatch(profile->xmatch.dfa, @@ -631,7 +632,7 @@ static struct aa_label *profile_transition(struct aa_profile *profile, { struct aa_label *new = NULL; const char *info = NULL, *name = NULL, *target = NULL; - unsigned int state = profile->file.start[AA_CLASS_FILE]; + aa_state_t state = profile->file.start[AA_CLASS_FILE]; struct aa_perms perms = {}; bool nonewprivs = false; int error = 0; @@ -727,7 +728,7 @@ 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[AA_CLASS_FILE]; + aa_state_t state = profile->file.start[AA_CLASS_FILE]; struct aa_perms perms = {}; const char *xname = NULL, *info = "change_profile onexec"; int error = -EACCES; diff --git a/security/apparmor/file.c b/security/apparmor/file.c index 7bddec3df75f..636efcade3f5 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -186,7 +186,7 @@ static int path_name(const char *op, struct aa_label *label, */ struct aa_perms default_perms = {}; struct aa_perms *aa_lookup_fperms(struct aa_policydb *file_rules, - unsigned int state, struct path_cond *cond) + aa_state_t state, struct path_cond *cond) { unsigned int index = ACCEPT_TABLE(file_rules->dfa)[state]; @@ -209,11 +209,11 @@ struct aa_perms *aa_lookup_fperms(struct aa_policydb *file_rules, * * Returns: the final state in @dfa when beginning @start and walking @name */ -unsigned int aa_str_perms(struct aa_policydb *file_rules, 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; + aa_state_t state; state = aa_dfa_match(file_rules->dfa, start, name); *perms = *(aa_lookup_fperms(file_rules, state, cond)); @@ -320,7 +320,7 @@ static int profile_path_link(struct aa_profile *profile, 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, diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index 736b8f655404..8c82cf279dc2 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -166,10 +166,10 @@ int aa_audit_file(struct aa_profile *profile, struct aa_perms *perms, const char *info, int error); struct aa_perms *aa_lookup_fperms(struct aa_policydb *file_rules, - unsigned int state, struct path_cond *cond); -unsigned int aa_str_perms(struct aa_policydb *file_rules, unsigned int start, - const char *name, struct path_cond *cond, - struct aa_perms *perms); + 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, diff --git a/security/apparmor/include/label.h b/security/apparmor/include/label.h index 860484c6f99a..1130ba10a152 100644 --- a/security/apparmor/include/label.h +++ b/security/apparmor/include/label.h @@ -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]) @@ -358,7 +358,7 @@ 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, + 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..f176f3ced2a3 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); 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/policy.h b/security/apparmor/include/policy.h index 31c0af876250..3a7d165e8fcc 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -78,7 +78,7 @@ struct aa_policydb { struct aa_dfa *dfa; struct aa_perms *perms; struct aa_domain trans; - unsigned int start[AA_CLASS_LAST + 1]; + aa_state_t start[AA_CLASS_LAST + 1]; }; static inline void aa_destroy_policydb(struct aa_policydb *policy) @@ -91,7 +91,7 @@ static inline void aa_destroy_policydb(struct aa_policydb *policy) } static inline struct aa_perms *aa_lookup_perms(struct aa_policydb *policy, - unsigned int state) + aa_state_t state) { unsigned int index = ACCEPT_TABLE(policy->dfa)[state]; @@ -239,7 +239,7 @@ 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, +static inline aa_state_t PROFILE_MEDIATES(struct aa_profile *profile, unsigned char class) { if (class <= AA_CLASS_LAST) @@ -249,13 +249,13 @@ static inline unsigned int PROFILE_MEDIATES(struct aa_profile *profile, profile->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 PROFILE_MEDIATES_AF(struct aa_profile *profile, + u16 AF) { + aa_state_t state = PROFILE_MEDIATES(profile, AA_CLASS_NET); __be16 be_af = cpu_to_be16(AF); if (!state) - return 0; + return DFA_NOMATCH; return aa_dfa_match_len(profile->policy.dfa, state, (char *) &be_af, 2); } diff --git a/security/apparmor/ipc.c b/security/apparmor/ipc.c index 3dbbc59d440d..7255a9d52372 100644 --- a/security/apparmor/ipc.c +++ b/security/apparmor/ipc.c @@ -79,7 +79,7 @@ static int profile_signal_perm(struct aa_profile *profile, struct common_audit_data *sa) { struct aa_perms perms; - unsigned int state; + aa_state_t state; if (profile_unconfined(profile) || !PROFILE_MEDIATES(profile, AA_CLASS_SIGNAL)) diff --git a/security/apparmor/label.c b/security/apparmor/label.c index 30cb68641c0f..3a967003fa7c 100644 --- a/security/apparmor/label.c +++ b/security/apparmor/label.c @@ -1265,9 +1265,9 @@ static inline bool label_is_visible(struct aa_profile *profile, * 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_profile *tp, + aa_state_t state) { const char *ns_name; @@ -1299,7 +1299,7 @@ static inline unsigned int match_component(struct aa_profile *profile, */ static int label_compound_match(struct aa_profile *profile, 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; @@ -1356,14 +1356,14 @@ fail: * check to be stacked. */ static int label_components_match(struct aa_profile *profile, - struct aa_label *label, unsigned int start, + 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) { @@ -1415,7 +1415,7 @@ 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, + aa_state_t state, bool subns, u32 request, struct aa_perms *perms) { int error = label_compound_match(profile, label, state, subns, request, diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index 974a217218a6..60deb4dc30c7 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -357,7 +357,7 @@ void aa_profile_match_label(struct aa_profile *profile, 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], diff --git a/security/apparmor/match.c b/security/apparmor/match.c index 3e9e1eaf990e..5095c26ca683 100644 --- a/security/apparmor/match.c +++ b/security/apparmor/match.c @@ -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 , 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 , 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 , 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 , 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 , 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 7594f3a3441e..84aaf25e5dee 100644 --- a/security/apparmor/mount.c +++ b/security/apparmor/mount.c @@ -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; @@ -217,12 +217,12 @@ 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_policydb *policy, 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(!policy); AA_BUG(!policy->dfa); @@ -567,7 +567,7 @@ static int profile_umount(struct aa_profile *profile, const struct path *path, { struct aa_perms perms = { }; const char *name = NULL, *info = NULL; - unsigned int state; + aa_state_t state; int error; AA_BUG(!profile); @@ -627,7 +627,7 @@ static struct aa_label *build_pivotroot(struct aa_profile *profile, 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); diff --git a/security/apparmor/net.c b/security/apparmor/net.c index fcfb97079e1b..d420d3aec3b8 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -109,7 +109,7 @@ int aa_profile_af_perm(struct aa_profile *profile, struct common_audit_data *sa, u32 request, u16 family, int type) { struct aa_perms perms = { }; - unsigned int state; + aa_state_t state; __be16 buffer[2]; AA_BUG(family >= AF_MAX); diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 0917412ba48f..3ea591d31be7 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -698,7 +698,7 @@ static u32 map_old_perms(u32 old) } static void compute_fperms_allow(struct aa_perms *perms, struct aa_dfa *dfa, - unsigned int state) + aa_state_t state) { perms->allow |= AA_MAY_GETATTR; @@ -710,7 +710,7 @@ static void compute_fperms_allow(struct aa_perms *perms, struct aa_dfa *dfa, } static struct aa_perms compute_fperms_user(struct aa_dfa *dfa, - unsigned int state) + aa_state_t state) { struct aa_perms perms = { }; @@ -725,7 +725,7 @@ static struct aa_perms compute_fperms_user(struct aa_dfa *dfa, } static struct aa_perms compute_fperms_other(struct aa_dfa *dfa, - unsigned int state) + aa_state_t state) { struct aa_perms perms = { }; @@ -748,8 +748,8 @@ static struct aa_perms compute_fperms_other(struct aa_dfa *dfa, */ static struct aa_perms *compute_fperms(struct aa_dfa *dfa) { - int state; - int state_count; + aa_state_t state; + unsigned int state_count; struct aa_perms *table; AA_BUG(!dfa); @@ -796,7 +796,7 @@ static u32 map_other(u32 x) } static struct aa_perms compute_perms_entry(struct aa_dfa *dfa, - unsigned int state) + aa_state_t state) { struct aa_perms perms = { }; @@ -817,8 +817,8 @@ static struct aa_perms compute_perms_entry(struct aa_dfa *dfa, static struct aa_perms *compute_perms(struct aa_dfa *dfa) { - int state; - int state_count; + unsigned int state; + unsigned int state_count; struct aa_perms *table; AA_BUG(!dfa); From 1b5a6198f5a9d0aa5497da0dc4bcd4fc166ee516 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 6 May 2022 18:57:12 -0700 Subject: [PATCH 19/64] apparmor: Fix abi check to include v8 abi The v8 abi is supported by the kernel but the userspace supported version check does not allow for it. This was missed when v8 was added due to a bug in the userspace compiler which was setting an older abi version for v8 encoding (which is forward compatible except on the network encoding). However it is possible to detect the network encoding by checking the policydb network support which the code does. The end result was that missing the abi flag worked until userspace was fixed and began correctly checking for the v8 abi version. Fixes: 56974a6fcfef ("apparmor: add base infastructure for socket mediation") Signed-off-by: John Johansen --- security/apparmor/policy_unpack.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 3ea591d31be7..0203e43460b6 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -1183,7 +1183,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, v8)) { audit_iface(NULL, NULL, NULL, "unsupported interface version", e, error); return error; From 1cf26c3d2c4c2098e39a9905174d7842b531e693 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sat, 7 May 2022 01:58:36 -0700 Subject: [PATCH 20/64] apparmor: fix apparmor mediating locking non-fs unix sockets the v8 and earlier policy does not encode the locking permission for no-fs unix sockets. However the kernel is enforcing mediation. Add the AA_MAY_LOCK perm to v8 and earlier computed perm mask which will grant permission for all current abi profiles, but still allow specifying auditing of the operation if needed. Link: http://bugs.launchpad.net/bugs/1780227 Signed-off-by: John Johansen --- security/apparmor/policy_unpack.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 0203e43460b6..2406c5c4caaf 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -31,6 +31,7 @@ #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 */ @@ -796,7 +797,8 @@ static u32 map_other(u32 x) } static struct aa_perms compute_perms_entry(struct aa_dfa *dfa, - aa_state_t state) + aa_state_t state, + u32 version) { struct aa_perms perms = { }; @@ -809,13 +811,15 @@ static struct aa_perms compute_perms_entry(struct aa_dfa *dfa, */ perms.allow |= map_other(dfa_other_allow(dfa, state)); + if (VERSION_LE(version, v8)) + perms.allow |= AA_MAY_LOCK; perms.audit |= map_other(dfa_other_audit(dfa, state)); perms.quiet |= map_other(dfa_other_quiet(dfa, state)); return perms; } -static struct aa_perms *compute_perms(struct aa_dfa *dfa) +static struct aa_perms *compute_perms(struct aa_dfa *dfa, u32 version) { unsigned int state; unsigned int state_count; @@ -831,7 +835,7 @@ static struct aa_perms *compute_perms(struct aa_dfa *dfa) /* zero init so skip the trap state (state == 0) */ for (state = 1; state < state_count; state++) - table[state] = compute_perms_entry(dfa, state); + table[state] = compute_perms_entry(dfa, state, version); return table; } @@ -1055,7 +1059,8 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) } if (!unpack_nameX(e, AA_STRUCTEND, NULL)) goto fail; - profile->policy.perms = compute_perms(profile->policy.dfa); + profile->policy.perms = compute_perms(profile->policy.dfa, + e->version); if (!profile->policy.perms) { info = "failed to remap policydb permission table"; goto fail; From 3c076531c5529c94cee330dffc4615ad02bb6edb Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 10 May 2022 02:21:22 -0700 Subject: [PATCH 21/64] apparmor: extend policydb permission set by making use of the xbits The policydb permission set has left the xbits unused. Make them available for mediation. Note: that this does not bring full auditing control of the permissions as there are not enough bits. The quieting of denials is provided as that is used more than forced auditing of allowed permissions. Signed-off-by: John Johansen --- security/apparmor/policy_unpack.c | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 2406c5c4caaf..e91883116663 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -38,6 +38,7 @@ #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 */ /* * The AppArmor interface treats data as a type byte followed by the @@ -796,6 +797,12 @@ static u32 map_other(u32 x) ((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) @@ -806,15 +813,31 @@ static struct aa_perms compute_perms_entry(struct aa_dfa *dfa, perms.audit = dfa_user_audit(dfa, state); perms.quiet = dfa_user_quiet(dfa, state); - /* for v5 perm mapping in the policydb, the other set is used - * to extend the general perm set + /* + * 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; } @@ -1188,7 +1211,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, v8)) { + if (VERSION_LT(e->version, v5) || VERSION_GT(e->version, v9)) { audit_iface(NULL, NULL, NULL, "unsupported interface version", e, error); return error; From b06a62ebf5a3f041b22def1608f1a8ab9bbfa951 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 16 May 2022 04:37:08 -0700 Subject: [PATCH 22/64] apparmor: move dfa perm macros into policy_unpack Now that the permission remapping macros aren't needed anywhere except during profile unpack, move them. Signed-off-by: John Johansen --- security/apparmor/include/file.h | 51 ------------------------------- security/apparmor/policy_unpack.c | 49 +++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 51 deletions(-) diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index 8c82cf279dc2..4212426020cb 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -109,57 +109,6 @@ 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, diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index e91883116663..32cca5f27b8f 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -671,6 +671,55 @@ static int datacmp(struct rhashtable_compare_arg *arg, const void *obj) return strcmp(data->key, *key); } +/* remap old accept table embedded permissions to separate permission table */ +static 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) + /** * map_old_perms - map old file perms layout to the new layout * @old: permission set in old mapping From ae6d35ed0a481824a8730c39d5b319c8a76ea00e Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sat, 16 Jul 2022 03:29:19 -0700 Subject: [PATCH 23/64] apparmor: extend xindex size Allow the xindex to have 2^24 entries. Signed-off-by: John Johansen --- security/apparmor/include/file.h | 19 +++++++++---------- security/apparmor/include/perms.h | 2 +- security/apparmor/policy_unpack.c | 8 ++++---- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index 4212426020cb..521c8568f6d4 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -88,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 0x00ffffff -#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 0x00000000 +#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 { diff --git a/security/apparmor/include/perms.h b/security/apparmor/include/perms.h index 1014a7bbc027..8739cef73549 100644 --- a/security/apparmor/include/perms.h +++ b/security/apparmor/include/perms.h @@ -78,7 +78,7 @@ struct aa_perms { u32 quiet; /* set only when ~allow | deny */ u32 hide; /* set only when ~allow | deny */ - u16 xindex; + u32 xindex; }; #define ALL_PERMS_MASK 0xffffffff diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 32cca5f27b8f..c578d9af785e 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -489,8 +489,8 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile) int i, size; size = unpack_array(e, NULL); - /* currently 4 exec bits and entries 0-3 are reserved iupcx */ - if (size > 16 - 4) + /* currently 2^24 bits entries 0-3 */ + if (size > (1 << 24)) goto fail; profile->file.trans.table = kcalloc(size, sizeof(char *), GFP_KERNEL); @@ -672,10 +672,10 @@ static int datacmp(struct rhashtable_compare_arg *arg, const void *obj) } /* remap old accept table embedded permissions to separate permission table */ -static u16 dfa_map_xindex(u16 mask) +static u32 dfa_map_xindex(u16 mask) { u16 old_index = (mask >> 10) & 0xf; - u16 index = 0; + u32 index = 0; if (mask & 0x100) index |= AA_X_UNSAFE; From caa9f579ca7255e9d6c25f072447d895c5928c97 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sun, 21 Aug 2022 22:48:32 -0700 Subject: [PATCH 24/64] apparmor: isolate policy backwards compatibility to its own file The details of mapping old policy into newer policy formats clutters up the unpack code and makes it possible to accidentally use old mappings in code, so isolate the mapping code into its own file. This will become more important when the dfa remapping code lands, as it will greatly expand the compat code base. Signed-off-by: John Johansen --- security/apparmor/Makefile | 3 +- security/apparmor/include/policy_compat.h | 33 +++ security/apparmor/include/policy_unpack.h | 1 + security/apparmor/policy_compat.c | 319 ++++++++++++++++++++++ security/apparmor/policy_unpack.c | 290 +------------------- 5 files changed, 359 insertions(+), 287 deletions(-) create mode 100644 security/apparmor/include/policy_compat.h create mode 100644 security/apparmor/policy_compat.c diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index ff23fcfefe19..4377123c2b98 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 clean-files := capability_names.h rlim_names.h net_names.h 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 eb5f7d7f132b..cdfbc8a54a9d 100644 --- a/security/apparmor/include/policy_unpack.h +++ b/security/apparmor/include/policy_unpack.h @@ -16,6 +16,7 @@ #include #include + struct aa_load_ent { struct list_head list; struct aa_profile *new; diff --git a/security/apparmor/policy_compat.c b/security/apparmor/policy_compat.c new file mode 100644 index 000000000000..1aa5cced935e --- /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 +#include + +#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; +} + +/** + * aa_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_unpack.c b/security/apparmor/policy_unpack.c index c578d9af785e..63196df2841b 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -27,18 +27,8 @@ #include "include/path.h" #include "include/policy.h" #include "include/policy_unpack.h" +#include "include/policy_compat.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 */ /* * The AppArmor interface treats data as a type byte followed by the @@ -671,270 +661,6 @@ static int datacmp(struct rhashtable_compare_arg *arg, const void *obj) return strcmp(data->key, *key); } -/* 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; -} - -/** - * aa_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; -} - /** * unpack_profile - unpack a serialized profile * @e: serialized data extent information (NOT NULL) @@ -1001,12 +727,10 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) } profile->xmatch_len = tmp; profile->xmatch.start[AA_CLASS_XMATCH] = DFA_START; - profile->xmatch.perms = compute_xmatch_perms(profile->xmatch.dfa); - if (!profile->xmatch.perms) { + if (aa_compat_map_xmatch(&profile->xmatch)) { info = "failed to convert xmatch permission table"; goto fail; } - remap_dfa_accept(profile->xmatch.dfa, 1); } /* disconnected attachment string is optional */ @@ -1131,14 +855,10 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) } if (!unpack_nameX(e, AA_STRUCTEND, NULL)) goto fail; - profile->policy.perms = compute_perms(profile->policy.dfa, - e->version); - if (!profile->policy.perms) { + if (aa_compat_map_policy(&profile->policy, e->version)) { info = "failed to remap policydb permission table"; goto fail; } - /* Do not remap internal dfas */ - remap_dfa_accept(profile->policy.dfa, 1); } else profile->policy.dfa = aa_get_dfa(nulldfa); @@ -1154,12 +874,10 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) "dfa_start")) /* default start state */ profile->file.start[AA_CLASS_FILE] = DFA_START; - profile->file.perms = compute_fperms(profile->file.dfa); - if (!profile->file.perms) { + if (aa_compat_map_file(&profile->file)) { info = "failed to remap file permission table"; goto fail; } - remap_dfa_accept(profile->file.dfa, 2); if (!unpack_trans_table(e, profile)) { info = "failed to unpack profile transition table"; goto fail; From 90917d5b6866df79d892087ba51b46c983d2fcfe Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sat, 16 Jul 2022 03:33:43 -0700 Subject: [PATCH 25/64] apparmor: extend permissions to support a label and tag string add indexes for label and tag entries. Rename the domain table to the str_table as its a shared string table with label and tags. Signed-off-by: John Johansen --- security/apparmor/domain.c | 18 ------------------ security/apparmor/include/domain.h | 6 ------ security/apparmor/include/lib.h | 6 ++++++ security/apparmor/include/perms.h | 2 ++ security/apparmor/include/policy.h | 6 ++++-- security/apparmor/lib.c | 19 +++++++++++++++++++ security/apparmor/policy_unpack.c | 2 +- 7 files changed, 32 insertions(+), 27 deletions(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 5883f0fc02d3..4cb046cf3a14 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -29,24 +29,6 @@ #include "include/policy.h" #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) 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/lib.h b/security/apparmor/include/lib.h index f176f3ced2a3..f1a29ab7ea1b 100644 --- a/security/apparmor/include/lib.h +++ b/security/apparmor/include/lib.h @@ -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/perms.h b/security/apparmor/include/perms.h index 8739cef73549..d66059fcebb4 100644 --- a/security/apparmor/include/perms.h +++ b/security/apparmor/include/perms.h @@ -79,6 +79,8 @@ struct aa_perms { u32 hide; /* set only when ~allow | deny */ u32 xindex; + u32 tag; /* tag string index, if present */ + u32 label; /* label string index, if present */ }; #define ALL_PERMS_MASK 0xffffffff diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index 3a7d165e8fcc..a28a662a0622 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -72,12 +72,14 @@ enum profile_mode { /* 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 { struct aa_dfa *dfa; struct aa_perms *perms; - struct aa_domain trans; + struct aa_str_table trans; aa_state_t start[AA_CLASS_LAST + 1]; }; @@ -86,7 +88,7 @@ static inline void aa_destroy_policydb(struct aa_policydb *policy) aa_put_dfa(policy->dfa); if (policy->perms) kvfree(policy->perms); - aa_free_domain_entries(&policy->trans); + aa_free_str_table(&policy->trans); } diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index 60deb4dc30c7..69aeb2dbd6d6 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -25,6 +25,25 @@ struct aa_perms allperms = { .allow = ALL_PERMS_MASK, .quiet = 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) diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 63196df2841b..df39ee8f4e03 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -534,7 +534,7 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile) return true; fail: - aa_free_domain_entries(&profile->file.trans); + aa_free_str_table(&profile->file.trans); e->pos = saved_pos; return false; } From 8c4b785a86be1219f7d50f7b38266c454d6a9bbc Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 19 Apr 2022 16:25:55 -0700 Subject: [PATCH 26/64] apparmor: add mediation class information to auditing Audit messages currently don't contain the mediation class which can make them less clear than they should be in some circumstances. With newer mediation classes coming this potential confusion will become worse. Fix this by adding the mediatin class to the messages. Signed-off-by: John Johansen --- security/apparmor/audit.c | 28 ++++++++++++++++++++++++++++ security/apparmor/capability.c | 2 +- security/apparmor/file.c | 2 +- security/apparmor/include/apparmor.h | 2 +- security/apparmor/include/audit.h | 8 ++++++-- security/apparmor/include/net.h | 1 + security/apparmor/ipc.c | 2 +- security/apparmor/lib.c | 2 +- security/apparmor/lsm.c | 3 ++- security/apparmor/mount.c | 2 +- security/apparmor/policy.c | 2 +- security/apparmor/policy_unpack.c | 2 +- security/apparmor/resource.c | 3 ++- security/apparmor/task.c | 2 +- 14 files changed, 48 insertions(+), 13 deletions(-) diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c index 704b0c895605..e638f7bc9f52 100644 --- a/security/apparmor/audit.c +++ b/security/apparmor/audit.c @@ -36,6 +36,28 @@ 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", + "unknown", + "unknown", + "unknown", + "net", + "unknown", + "label", + "lsm", +}; + + /* * Currently AppArmor auditing is fed straight into the audit framework. * @@ -65,6 +87,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..6cabd6109f12 100644 --- a/security/apparmor/capability.c +++ b/security/apparmor/capability.c @@ -148,7 +148,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/file.c b/security/apparmor/file.c index 636efcade3f5..69d936d04f94 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; diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h index 8fd66a4ca0b8..6d9ca075fcb9 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 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/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/ipc.c b/security/apparmor/ipc.c index 7255a9d52372..4ecaf2ba26c5 100644 --- a/security/apparmor/ipc.c +++ b/security/apparmor/ipc.c @@ -98,7 +98,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/lib.c b/security/apparmor/lib.c index 69aeb2dbd6d6..768cc182e9ca 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -143,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); diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index ec873ff0a4bb..784709286a62 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -647,7 +647,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; diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c index 84aaf25e5dee..02d8215cb9fd 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; diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index cdcf26c9bed5..6222236de021 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -617,7 +617,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; diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index df39ee8f4e03..4bf33bd0ca69 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -100,7 +100,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; diff --git a/security/apparmor/resource.c b/security/apparmor/resource.c index 1ae4874251a9..cc018469e22d 100644 --- a/security/apparmor/resource.c +++ b/security/apparmor/resource.c @@ -53,7 +53,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; diff --git a/security/apparmor/task.c b/security/apparmor/task.c index 503dc0877fb1..b19900f85c14 100644 --- a/security/apparmor/task.c +++ b/security/apparmor/task.c @@ -285,7 +285,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), From 22fac8a051191113becc0da62bf88b0ba8ce6c08 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 17 Dec 2019 15:40:41 -0800 Subject: [PATCH 27/64] apparmor: add user mode flag Allow the profile to contain a user mode prompt flag. This works similar to complain mode but will try to send messages to a userspace daemon. If the daemon is not present or timesout regular informent will occur. Signed-off-by: John Johansen --- security/apparmor/include/policy.h | 3 +++ security/apparmor/include/policy_unpack.h | 1 + security/apparmor/lib.c | 7 ++----- security/apparmor/policy.c | 1 + security/apparmor/policy_unpack.c | 2 ++ 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index a28a662a0622..9fc5d7fa36e8 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,6 +69,7 @@ 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 */ }; diff --git a/security/apparmor/include/policy_unpack.h b/security/apparmor/include/policy_unpack.h index cdfbc8a54a9d..1e10e360a0ec 100644 --- a/security/apparmor/include/policy_unpack.h +++ b/security/apparmor/include/policy_unpack.h @@ -36,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; diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index 768cc182e9ca..b0fcec893274 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -327,11 +327,8 @@ 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; - */ + else if (USER_MODE(profile)) + perms->prompt = ALL_PERMS_MASK; } /** diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 6222236de021..3c3a5263695d 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", }; diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 4bf33bd0ca69..04e9fca250df 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -761,6 +761,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; } From a0792e2ceddc1bff8bda34a82b5ef7f00cbe7a9f Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 23 Aug 2022 01:06:15 -0700 Subject: [PATCH 28/64] apparmor: make transition table unpack generic so it can be reused Currently the transition table is tied to the file dfa. Make it so we can unpack a transition table against any dfa. Signed-off-by: John Johansen --- security/apparmor/policy_unpack.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 04e9fca250df..052e3b914c18 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -466,13 +466,14 @@ 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; /* exec table is optional */ if (unpack_nameX(e, AA_STRUCT, "xtable")) { @@ -482,12 +483,10 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile) /* currently 2^24 bits entries 0-3 */ if (size > (1 << 24)) 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 = unpack_strdup(e, &str, NULL); @@ -496,7 +495,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; @@ -530,11 +529,14 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile) goto fail; if (!unpack_nameX(e, AA_STRUCTEND, NULL)) goto fail; + + strs->table = table; + strs->size = size; } return true; fail: - aa_free_str_table(&profile->file.trans); + kfree_sensitive(table); e->pos = saved_pos; return false; } @@ -880,7 +882,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) info = "failed to remap file permission table"; goto fail; } - if (!unpack_trans_table(e, profile)) { + if (!unpack_trans_table(e, &profile->file.trans)) { info = "failed to unpack profile transition table"; goto fail; } From ad596ea74e746d60bb7e13f3adde097a08b2089b Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 18 Jul 2022 16:53:17 -0700 Subject: [PATCH 29/64] apparmor: group dfa policydb unpacking There are currently three policydb rule groupings (xmatch, file, policydb) that each do their own slightly different thing. Group them into a single routine and unify. This extends/unifies dfa features by - all dfas are allowed having an optional start field - all dfas are allowed having a string/transition table Signed-off-by: John Johansen --- security/apparmor/policy_unpack.c | 101 +++++++++++++++++++----------- 1 file changed, 63 insertions(+), 38 deletions(-) diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 052e3b914c18..a1fe0a5e8e57 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -648,6 +648,54 @@ fail: return false; } +static int unpack_pdb(struct aa_ext *e, struct aa_policydb *policy, + bool required_dfa, bool required_trans, + const char **info) +{ + int i; + + policy->dfa = unpack_dfa(e); + if (IS_ERR(policy->dfa)) { + int error = PTR_ERR(policy->dfa); + + policy->dfa = NULL; + *info = "failed to unpack - dfa"; + return error; + } else if (!policy->dfa) { + if (required_dfa) { + *info = "missing required dfa"; + return -EPROTO; + } + 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 (!unpack_u32(e, &policy->start[0], "start")) + /* default start state */ + policy->start[0] = DFA_START; + if (!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"; + return -EPROTO; + } + /* TODO: move compat mapping here, requires dfa merging first */ + +out: + return 0; +} + static u32 strhash(const void *data, u32 len, u32 seed) { const char * const *key = data; @@ -679,7 +727,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; @@ -714,13 +762,10 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) (void) unpack_str(e, &profile->attach, "attach"); /* xmatch is optional and may be NULL */ - profile->xmatch.dfa = unpack_dfa(e); - if (IS_ERR(profile->xmatch.dfa)) { - error = PTR_ERR(profile->xmatch.dfa); - profile->xmatch.dfa = NULL; - info = "bad xmatch"; + error = unpack_pdb(e, &profile->xmatch, false, false, &info); + if (error) goto fail; - } + /* neither xmatch_len not xmatch_perms are optional if xmatch is set */ if (profile->xmatch.dfa) { if (!unpack_u32(e, &tmp, NULL)) { @@ -838,25 +883,16 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) if (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; + error = unpack_pdb(e, &profile->policy, true, false, &info); + if (error) goto fail; - } else if (!profile->policy.dfa) { - error = -EPROTO; - goto fail; - } - if (!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(profile->policy.dfa, profile->policy.start[0], + AA_CLASS_FILE)) + profile->policy.start[AA_CLASS_FILE] = + aa_dfa_next(profile->policy.dfa, + profile->policy.start[0], + AA_CLASS_FILE); if (!unpack_nameX(e, AA_STRUCTEND, NULL)) goto fail; if (aa_compat_map_policy(&profile->policy, e->version)) { @@ -867,25 +903,14 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) profile->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, &profile->file, false, true, &info); + if (error) { goto fail; } else if (profile->file.dfa) { - if (!unpack_u32(e, &profile->file.start[AA_CLASS_FILE], - "dfa_start")) - /* default start state */ - profile->file.start[AA_CLASS_FILE] = DFA_START; if (aa_compat_map_file(&profile->file)) { info = "failed to remap file permission table"; goto fail; } - if (!unpack_trans_table(e, &profile->file.trans)) { - info = "failed to unpack profile transition table"; - goto fail; - } } else if (profile->policy.dfa && profile->policy.start[AA_CLASS_FILE]) { profile->file.dfa = aa_get_dfa(profile->policy.dfa); From 371e50a0b19f9765bfb9e4f172e72f4e9a4625bc Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 26 Aug 2022 09:26:57 -0700 Subject: [PATCH 30/64] apparmor: make unpack_array return a trianary value currently unpack_array() does not return an error nor whether the array is not present. The ability to detect an error or the array not being present is needed so rework the unpack_array() to return the needed information. Signed-off-by: John Johansen --- security/apparmor/policy_unpack.c | 43 ++++++++++++++++---------- security/apparmor/policy_unpack_test.c | 12 +++---- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index a1fe0a5e8e57..7d3b3e664c1c 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -67,6 +67,11 @@ struct aa_ext { u32 version; }; +#define tri int +#define TRI_TRUE 1 +#define TRI_NONE 0 +#define TRI_FALSE -1 + /* audit callback for unpack fields */ static void audit_cb(struct audit_buffer *ab, void *va) { @@ -344,22 +349,22 @@ fail: return false; } -static size_t unpack_array(struct aa_ext *e, const char *name) +static tri unpack_array(struct aa_ext *e, const char *name, u16 *size) { void *pos = e->pos; if (unpack_nameX(e, AA_ARRAY, name)) { - int size; if (!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 TRI_TRUE; } + return TRI_NONE; fail: e->pos = pos; - return 0; + return TRI_FALSE; } static size_t unpack_blob(struct aa_ext *e, char **blob, const char *name) @@ -477,11 +482,12 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_str_table *strs) /* exec table is optional */ if (unpack_nameX(e, AA_STRUCT, "xtable")) { - int i, size; + u16 size; + int i; - size = unpack_array(e, NULL); - /* currently 2^24 bits entries 0-3 */ - if (size > (1 << 24)) + if (unpack_array(e, NULL, &size) != TRI_TRUE || + size > (1 << 24)) + /* currently 2^24 bits entries 0-3 */ goto fail; table = kcalloc(size, sizeof(char *), GFP_KERNEL); if (!table) @@ -546,9 +552,11 @@ static bool unpack_xattrs(struct aa_ext *e, struct aa_profile *profile) void *pos = e->pos; if (unpack_nameX(e, AA_STRUCT, "xattrs")) { - int i, size; + u16 size; + int i; - size = unpack_array(e, NULL); + if (unpack_array(e, NULL, &size) != TRI_TRUE) + goto fail; profile->xattr_count = size; profile->xattrs = kcalloc(size, sizeof(char *), GFP_KERNEL); if (!profile->xattrs) @@ -573,10 +581,12 @@ fail: static bool unpack_secmark(struct aa_ext *e, struct aa_profile *profile) { void *pos = e->pos; - int i, size; + u16 size; + int i; if (unpack_nameX(e, AA_STRUCT, "secmark")) { - size = unpack_array(e, NULL); + if (unpack_array(e, NULL, &size) != TRI_TRUE) + goto fail; profile->secmark = kcalloc(size, sizeof(struct aa_secmark), GFP_KERNEL); @@ -620,14 +630,15 @@ static bool unpack_rlimits(struct aa_ext *e, struct aa_profile *profile) /* rlimits are optional */ if (unpack_nameX(e, AA_STRUCT, "rlimits")) { - int i, size; + u16 size; + int i; u32 tmp = 0; if (!unpack_u32(e, &tmp, NULL)) goto fail; profile->rlimits.mask = tmp; - size = unpack_array(e, NULL); - if (size > RLIM_NLIMITS) + if (unpack_array(e, NULL, &size) != TRI_TRUE || + size > RLIM_NLIMITS) goto fail; for (i = 0; i < size; i++) { u64 tmp2 = 0; diff --git a/security/apparmor/policy_unpack_test.c b/security/apparmor/policy_unpack_test.c index 0a969b2e03db..1a43d538c4c0 100644 --- a/security/apparmor/policy_unpack_test.c +++ b/security/apparmor/policy_unpack_test.c @@ -144,8 +144,8 @@ static void policy_unpack_test_unpack_array_with_null_name(struct kunit *test) puf->e->pos += TEST_ARRAY_BUF_OFFSET; - array_size = unpack_array(puf->e, NULL); - + KUNIT_EXPECT_EQ(test, unpack_array(puf->e, NULL, &array_size), + TRI_TRUE); 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); @@ -159,8 +159,8 @@ static void policy_unpack_test_unpack_array_with_name(struct kunit *test) puf->e->pos += TEST_NAMED_ARRAY_BUF_OFFSET; - array_size = unpack_array(puf->e, name); - + KUNIT_EXPECT_EQ(test, unpack_array(puf->e, name, &array_size), + TRI_TRUE); 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); @@ -175,8 +175,8 @@ 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 = unpack_array(puf->e, name); - + KUNIT_EXPECT_EQ(test, unpack_array(puf->e, name, &array_size), + TRI_TRUE); KUNIT_EXPECT_EQ(test, array_size, 0); KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, puf->e->start + TEST_NAMED_ARRAY_BUF_OFFSET); From fd1b2b95a21177eaa9e26989637e477be4d93b2f Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 26 Aug 2022 08:53:42 -0700 Subject: [PATCH 31/64] apparmor: add the ability for policy to specify a permission table Currently permissions are encoded in the dfa accept entries that are then mapped to an internal permission structure. This limits the permissions that userspace can specify, so allow userspace to directly specify the permission table. Signed-off-by: John Johansen --- security/apparmor/include/policy.h | 5 +- security/apparmor/policy_unpack.c | 104 ++++++++++++++++++++++++++--- 2 files changed, 98 insertions(+), 11 deletions(-) diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index 9fc5d7fa36e8..2c39bd389f87 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -81,7 +81,10 @@ enum profile_mode { */ struct aa_policydb { struct aa_dfa *dfa; - struct aa_perms *perms; + struct { + struct aa_perms *perms; + u32 size; + }; struct aa_str_table trans; aa_state_t start[AA_CLASS_LAST + 1]; }; diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 7d3b3e664c1c..b85dbdde8939 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -435,10 +435,11 @@ static int unpack_strdup(struct aa_ext *e, char **string, const char *name) /** * 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; @@ -454,8 +455,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); @@ -659,23 +658,104 @@ fail: return false; } +static bool unpack_perm(struct aa_ext *e, u32 version, struct aa_perms *perm) +{ + bool res; + + if (version != 1) + return false; + + res = unpack_u32(e, &perm->allow, NULL); + res = res && unpack_u32(e, &perm->allow, NULL); + res = res && unpack_u32(e, &perm->deny, NULL); + res = res && unpack_u32(e, &perm->subtree, NULL); + res = res && unpack_u32(e, &perm->cond, NULL); + res = res && unpack_u32(e, &perm->kill, NULL); + res = res && unpack_u32(e, &perm->complain, NULL); + res = res && unpack_u32(e, &perm->prompt, NULL); + res = res && unpack_u32(e, &perm->audit, NULL); + res = res && unpack_u32(e, &perm->quiet, NULL); + res = res && unpack_u32(e, &perm->hide, NULL); + res = res && unpack_u32(e, &perm->xindex, NULL); + res = res && unpack_u32(e, &perm->tag, NULL); + res = res && unpack_u32(e, &perm->label, NULL); + + return res; +} + +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 (unpack_nameX(e, AA_STRUCT, "perms")) { + int i; + u32 version; + + if (!unpack_u32(e, &version, "version")) + goto fail_reset; + if (unpack_array(e, NULL, &size) != TRI_TRUE) + 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 (!unpack_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + if (!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) { - int i; + void *pos = e->pos; + int i, flags, error = -EPROTO; - policy->dfa = unpack_dfa(e); + policy->size = unpack_perms_table(e, &policy->perms); + if (policy->size < 0) { + error = policy->size; + policy->perms = NULL; + *info = "failed to unpack - perms"; + goto fail; + } else 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)) { - int error = PTR_ERR(policy->dfa); - + error = PTR_ERR(policy->dfa); policy->dfa = NULL; *info = "failed to unpack - dfa"; - return error; + goto fail; } else if (!policy->dfa) { if (required_dfa) { *info = "missing required dfa"; - return -EPROTO; + goto fail; } goto out; } @@ -699,12 +779,16 @@ static int unpack_pdb(struct aa_ext *e, struct aa_policydb *policy, } if (!unpack_trans_table(e, &policy->trans) && required_trans) { *info = "failed to unpack profile transition table"; - return -EPROTO; + goto fail; } /* TODO: move compat mapping here, requires dfa merging first */ out: return 0; + +fail: + e->pos = pos; + return error; } static u32 strhash(const void *data, u32 len, u32 seed) From 670f31774ab6bf8e2d756f27444b035b9be8a0c9 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 26 Aug 2022 13:32:34 -0700 Subject: [PATCH 32/64] apparmor: verify permission table indexes While the dfa xindex's are verified, the indexes in the permission table are not currently verified. Fix this. Signed-off-by: John Johansen --- security/apparmor/policy_unpack.c | 35 ++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index b85dbdde8939..312bd632a472 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -781,8 +781,9 @@ static int unpack_pdb(struct aa_ext *e, struct aa_policydb *policy, *info = "failed to unpack profile transition table"; goto fail; } - /* TODO: move compat mapping here, requires dfa merging first */ + /* TODO: move compat mapping here, requires dfa merging first */ + /* TODO: move verify here, it has to be done after compat mappings */ out: return 0; @@ -1149,6 +1150,22 @@ static bool verify_dfa_xindex(struct aa_dfa *dfa, int table_size) return true; } +static bool verify_perm_indexes(struct aa_policydb *pdb) +{ + int i; + + for (i = 0; i < pdb->size; i++) { + if (pdb->perms[i].xindex >= pdb->trans.size) + return false; + if (pdb->perms[i].tag >= pdb->trans.size) + return false; + if (pdb->perms[i].label >= pdb->trans.size) + return false; + } + + return true; +} + /** * verify_profile - Do post unpack analysis to verify profile consistency * @profile: profile to verify (NOT NULL) @@ -1170,6 +1187,22 @@ static int verify_profile(struct aa_profile *profile) return -EPROTO; } + if (!verify_perm_indexes(&profile->file)) { + audit_iface(profile, NULL, NULL, + "Unpack: Invalid perm index", NULL, -EPROTO); + return -EPROTO; + } + if (!verify_perm_indexes(&profile->policy)) { + audit_iface(profile, NULL, NULL, + "Unpack: Invalid perm index", NULL, -EPROTO); + return -EPROTO; + } + if (!verify_perm_indexes(&profile->xmatch)) { + audit_iface(profile, NULL, NULL, + "Unpack: Invalid perm index", NULL, -EPROTO); + return -EPROTO; + } + return 0; } From 0bece4fa97a2bd397da66d4fced78f76eb214a3e Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 5 Sep 2022 23:53:29 -0700 Subject: [PATCH 33/64] apparmor: make sure perm indexes are accumulated accumulate permission indexes on a first encountered basis. This favors original rulesets so that new ones can not override without profile replacement. Signed-off-by: John Johansen --- security/apparmor/include/file.h | 4 ++-- security/apparmor/include/perms.h | 9 +++++++++ security/apparmor/lib.c | 14 ++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index 521c8568f6d4..1a1c0f0c5071 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -88,10 +88,10 @@ 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 0x00ffffff +#define AA_X_INDEX_MASK AA_INDEX_MASK #define AA_X_TYPE_MASK 0x0c000000 -#define AA_X_NONE 0x00000000 +#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# */ diff --git a/security/apparmor/include/perms.h b/security/apparmor/include/perms.h index d66059fcebb4..0de8c3fb090d 100644 --- a/security/apparmor/include/perms.h +++ b/security/apparmor/include/perms.h @@ -78,11 +78,20 @@ struct aa_perms { 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; diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index b0fcec893274..d6a8c361025b 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -348,6 +348,13 @@ void aa_perms_accum_raw(struct aa_perms *accum, struct aa_perms *addend) 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; } /** @@ -367,6 +374,13 @@ void aa_perms_accum(struct aa_perms *accum, struct aa_perms *addend) 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; } void aa_profile_match_label(struct aa_profile *profile, struct aa_label *label, From 3dfd16ab697ff23973b6fbb89808372bcd008dd1 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 5 Sep 2022 23:57:51 -0700 Subject: [PATCH 34/64] apparmor: cleanup: move perm accumulation into perms.h Perm accumulation is going to be used much more frequently so let the compiler figure out if it can be optimized when used. Signed-off-by: John Johansen --- security/apparmor/include/perms.h | 53 +++++++++++++++++++++++++++++++ security/apparmor/lib.c | 52 ------------------------------ 2 files changed, 53 insertions(+), 52 deletions(-) diff --git a/security/apparmor/include/perms.h b/security/apparmor/include/perms.h index 0de8c3fb090d..9fa71957ac3a 100644 --- a/security/apparmor/include/perms.h +++ b/security/apparmor/include/perms.h @@ -96,6 +96,59 @@ struct aa_perms { 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) \ ({ \ diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index d6a8c361025b..10e3b11e02ad 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -331,58 +331,6 @@ void aa_apply_modes_to_perms(struct aa_profile *profile, struct aa_perms *perms) perms->prompt = ALL_PERMS_MASK; } -/** - * 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->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 - */ -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; -} - void aa_profile_match_label(struct aa_profile *profile, struct aa_label *label, int type, u32 request, struct aa_perms *perms) { From 3bf3d728a58d7dcf2bbf179e3263fb8651f6097b Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 6 Sep 2022 00:38:20 -0700 Subject: [PATCH 35/64] apparmor: verify loaded permission bits masks don't overlap Add an additional verification that loaded permission sets don't overlap in ways that are not intended. This will help ensure that permission accumulation can't result in an invalid permission set. Signed-off-by: John Johansen --- security/apparmor/policy_unpack.c | 34 +++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 312bd632a472..5a78aaa0eea4 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -1150,11 +1150,37 @@ static bool verify_dfa_xindex(struct aa_dfa *dfa, int table_size) return true; } -static bool verify_perm_indexes(struct aa_policydb *pdb) +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 (pdb->perms[i].tag >= pdb->trans.size) @@ -1187,17 +1213,17 @@ static int verify_profile(struct aa_profile *profile) return -EPROTO; } - if (!verify_perm_indexes(&profile->file)) { + if (!verify_perms(&profile->file)) { audit_iface(profile, NULL, NULL, "Unpack: Invalid perm index", NULL, -EPROTO); return -EPROTO; } - if (!verify_perm_indexes(&profile->policy)) { + if (!verify_perms(&profile->policy)) { audit_iface(profile, NULL, NULL, "Unpack: Invalid perm index", NULL, -EPROTO); return -EPROTO; } - if (!verify_perm_indexes(&profile->xmatch)) { + if (!verify_perms(&profile->xmatch)) { audit_iface(profile, NULL, NULL, "Unpack: Invalid perm index", NULL, -EPROTO); return -EPROTO; From 217af7e2f4deb629aaa49622685ccfee923898ca Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 29 Jul 2022 17:17:31 -0700 Subject: [PATCH 36/64] apparmor: refactor profile rules and attachments In preparation for moving from a single set of rules and a single attachment to multiple rulesets and attachments separate from the profile refactor attachment information and ruleset info into their own structures. Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 27 ++++--- security/apparmor/capability.c | 12 +-- security/apparmor/domain.c | 81 +++++++++++--------- security/apparmor/file.c | 14 ++-- security/apparmor/include/label.h | 9 ++- security/apparmor/include/perms.h | 3 +- security/apparmor/include/policy.h | 84 ++++++++++++-------- security/apparmor/ipc.c | 9 ++- security/apparmor/label.c | 45 ++++++----- security/apparmor/lib.c | 13 ++-- security/apparmor/lsm.c | 4 +- security/apparmor/mount.c | 31 ++++---- security/apparmor/net.c | 24 +++--- security/apparmor/policy.c | 44 +++++++---- security/apparmor/policy_ns.c | 4 +- security/apparmor/policy_unpack.c | 118 +++++++++++++++-------------- security/apparmor/resource.c | 15 ++-- security/apparmor/task.c | 10 +-- 18 files changed, 308 insertions(+), 239 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index fb9d2ccb34d6..84ef8b400b40 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -611,30 +611,29 @@ 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 = &profile->rules; struct aa_perms tmp = { }; - struct aa_dfa *dfa; 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[AA_CLASS_FILE], + if (rules->file.dfa && *match_str == AA_CLASS_FILE) { + state = aa_dfa_match_len(rules->file.dfa, + rules->file.start[AA_CLASS_FILE], match_str + 1, match_len - 1); if (state) { struct path_cond cond = { }; - tmp = *(aa_lookup_fperms(&(profile->file), 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) - tmp = *aa_lookup_perms(&profile->policy, state); + tmp = *aa_lookup_perms(&rules->policy, state); } aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum_raw(perms, &tmp); @@ -1093,9 +1092,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.dfa) + if (profile->attach.xmatch_str) + seq_printf(seq, "%s\n", profile->attach.xmatch_str); + else if (profile->attach.xmatch.dfa) seq_puts(seq, "\n"); else seq_printf(seq, "%s\n", profile->base.name); diff --git a/security/apparmor/capability.c b/security/apparmor/capability.c index 6cabd6109f12..b66ec63e2a48 100644 --- a/security/apparmor/capability.c +++ b/security/apparmor/capability.c @@ -64,6 +64,7 @@ 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 = &profile->rules; struct audit_cache *ent; int type = AUDIT_APPARMOR_AUTO; @@ -72,13 +73,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 +115,11 @@ 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 = &profile->rules; 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; diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 4cb046cf3a14..ad035d14cfc5 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -81,19 +81,20 @@ 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 = &profile->rules; 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); } /** @@ -117,6 +118,7 @@ static int label_compound_match(struct aa_profile *profile, aa_state_t state, bool subns, u32 request, struct aa_perms *perms) { + struct aa_ruleset *rules = &profile->rules; struct aa_profile *tp; struct label_it i; struct path_cond cond = { }; @@ -139,12 +141,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_lookup_fperms(&(profile->file), state, &cond)); + *perms = *(aa_lookup_fperms(&(rules->file), state, &cond)); aa_apply_modes_to_perms(profile, perms); if ((perms->allow & request) != request) return -EACCES; @@ -177,6 +179,7 @@ static int label_components_match(struct aa_profile *profile, aa_state_t start, bool subns, u32 request, struct aa_perms *perms) { + struct aa_ruleset *rules = &profile->rules; struct aa_profile *tp; struct label_it i; struct aa_perms tmp; @@ -197,7 +200,7 @@ static int label_components_match(struct aa_profile *profile, return 0; next: - tmp = *(aa_lookup_fperms(&(profile->file), state, &cond)); + tmp = *(aa_lookup_fperms(&(rules->file), state, &cond)); aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum(perms, &tmp); label_for_each_cont(i, label, tp) { @@ -206,7 +209,7 @@ next: state = match_component(profile, tp, stack, start); if (!state) goto fail; - tmp = *(aa_lookup_fperms(&(profile->file), state, &cond)); + tmp = *(aa_lookup_fperms(&(rules->file), state, &cond)); aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum(perms, &tmp); } @@ -296,18 +299,19 @@ static int aa_xattrs_match(const struct linux_binprm *bprm, ssize_t size; struct dentry *d; char *value = NULL; - int value_size = 0, ret = profile->xattr_count; + struct aa_attachment *attach = &profile->attach; + int 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.dfa, 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 index, perm; @@ -317,20 +321,20 @@ static int aa_xattrs_match(const struct linux_binprm *bprm, * that not present xattr can be distinguished from a 0 * length value or rule that matches any value */ - state = aa_dfa_null_transition(profile->xmatch.dfa, + state = aa_dfa_null_transition(attach->xmatch.dfa, state); /* Check xattr value */ - state = aa_dfa_match_len(profile->xmatch.dfa, state, + state = aa_dfa_match_len(attach->xmatch.dfa, state, value, size); - index = ACCEPT_TABLE(profile->xmatch.dfa)[state]; - perm = profile->xmatch.perms[index].allow; + index = ACCEPT_TABLE(attach->xmatch.dfa)[state]; + perm = attach->xmatch.perms[index].allow; if (!(perm & MAY_EXEC)) { ret = -EINVAL; goto out; } } /* transition to next element */ - state = aa_dfa_outofband_transition(profile->xmatch.dfa, state); + state = aa_dfa_outofband_transition(attach->xmatch.dfa, state); if (size < 0) { /* * No xattr match, so verify if transition to @@ -382,6 +386,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; @@ -397,16 +403,16 @@ restart: * as another profile, signal a conflict and refuse to * match. */ - if (profile->xmatch.dfa) { + if (attach->xmatch.dfa) { unsigned int count; aa_state_t state; u32 index, perm; - state = aa_dfa_leftmatch(profile->xmatch.dfa, - profile->xmatch.start[AA_CLASS_XMATCH], + state = aa_dfa_leftmatch(attach->xmatch.dfa, + attach->xmatch.start[AA_CLASS_XMATCH], name, &count); - index = ACCEPT_TABLE(profile->xmatch.dfa)[state]; - perm = profile->xmatch.perms[index].allow; + index = ACCEPT_TABLE(attach->xmatch.dfa)[state]; + perm = attach->xmatch.perms[index].allow; /* any accepting state means a valid match. */ if (perm & MAY_EXEC) { int ret = 0; @@ -414,7 +420,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)) @@ -453,7 +459,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; } @@ -497,6 +503,7 @@ 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 = &profile->rules; struct aa_label *label = NULL; u32 xtype = xindex & AA_X_TYPE_MASK; int index = xindex & AA_X_INDEX_MASK; @@ -507,7 +514,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; @@ -546,6 +553,7 @@ static struct aa_label *x_to_label(struct aa_profile *profile, const char **lookupname, const char **info) { + struct aa_ruleset *rules = &profile->rules; struct aa_label *new = NULL; struct aa_ns *ns = profile->ns; u32 xtype = xindex & AA_X_TYPE_MASK; @@ -558,7 +566,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); @@ -612,9 +620,10 @@ static struct aa_label *profile_transition(struct aa_profile *profile, char *buffer, struct path_cond *cond, bool *secure_exec) { + struct aa_ruleset *rules = &profile->rules; struct aa_label *new = NULL; const char *info = NULL, *name = NULL, *target = NULL; - aa_state_t state = profile->file.start[AA_CLASS_FILE]; + aa_state_t state = rules->file.start[AA_CLASS_FILE]; struct aa_perms perms = {}; bool nonewprivs = false; int error = 0; @@ -648,7 +657,7 @@ static struct aa_label *profile_transition(struct aa_profile *profile, } /* find exec permissions for name */ - state = aa_str_perms(&(profile->file), state, name, cond, &perms); + state = aa_str_perms(&(rules->file), state, name, cond, &perms); if (perms.allow & MAY_EXEC) { /* exec permission determine how to transition */ new = x_to_label(profile, bprm, name, perms.xindex, &target, @@ -710,7 +719,8 @@ static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec, char *buffer, struct path_cond *cond, bool *secure_exec) { - aa_state_t state = profile->file.start[AA_CLASS_FILE]; + struct aa_ruleset *rules = &profile->rules; + 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; @@ -743,7 +753,7 @@ static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec, } /* find exec permissions for name */ - state = aa_str_perms(&(profile->file), state, xname, cond, &perms); + state = aa_str_perms(&(rules->file), state, xname, cond, &perms); if (!(perms.allow & AA_MAY_ONEXEC)) { info = "no change_onexec valid for executable"; goto audit; @@ -752,7 +762,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) { @@ -1249,12 +1259,13 @@ 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 = &profile->rules; const char *info = NULL; int error = 0; if (!error) error = change_profile_perms(profile, target, stack, request, - profile->file.start[AA_CLASS_FILE], + rules->file.start[AA_CLASS_FILE], perms); if (error) error = aa_audit_file(profile, perms, op, request, name, diff --git a/security/apparmor/file.c b/security/apparmor/file.c index 69d936d04f94..ef5d98f81a2b 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -224,11 +224,12 @@ 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 = &profile->rules; int e = 0; if (profile_unconfined(profile)) return 0; - aa_str_perms(&(profile->file), profile->file.start[AA_CLASS_FILE], + aa_str_perms(&(rules->file), rules->file.start[AA_CLASS_FILE], name, cond, perms); if (request & ~perms->allow) e = -EACCES; @@ -316,6 +317,7 @@ static int profile_path_link(struct aa_profile *profile, const struct path *target, char *buffer2, struct path_cond *cond) { + struct aa_ruleset *rules = &profile->rules; const char *lname, *tname = NULL; struct aa_perms lperms = {}, perms; const char *info = NULL; @@ -336,16 +338,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), - profile->file.start[AA_CLASS_FILE], lname, + state = aa_str_perms(&(rules->file), + rules->file.start[AA_CLASS_FILE], lname, cond, &lperms); if (!(lperms.allow & AA_MAY_LINK)) goto audit; /* test to see if target can be paired with link */ - state = aa_dfa_null_transition(profile->file.dfa, state); - aa_str_perms(&(profile->file), state, tname, cond, &perms); + state = aa_dfa_null_transition(rules->file.dfa, state); + aa_str_perms(&(rules->file), state, tname, cond, &perms); /* force audit/quiet masks for link are stored in the second entry * in the link pair. @@ -367,7 +369,7 @@ 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), profile->file.start[AA_CLASS_FILE], + aa_str_perms(&(rules->file), rules->file.start[AA_CLASS_FILE], tname, cond, &perms); /* AA_MAY_LINK is not considered in the subset test */ diff --git a/security/apparmor/include/label.h b/security/apparmor/include/label.h index 1130ba10a152..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; \ } \ @@ -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, - aa_state_t 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/perms.h b/security/apparmor/include/perms.h index 9fa71957ac3a..797a7a00644d 100644 --- a/security/apparmor/include/perms.h +++ b/security/apparmor/include/perms.h @@ -207,7 +207,8 @@ void aa_apply_modes_to_perms(struct aa_profile *profile, 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 2c39bd389f87..9ee2c05e2895 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -123,6 +123,43 @@ struct aa_data { struct rhash_head head; }; +/* struct aa_ruleset - data covering mediation rules + * @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 { + 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 + * @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 ...) @@ -130,18 +167,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 @@ -166,27 +198,13 @@ struct aa_profile { struct aa_ns *ns; const char *rename; - const char *attach; - struct aa_policydb 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_policydb 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 aa_ruleset rules; struct aa_loaddata *rawdata; unsigned char *hash; @@ -247,24 +265,24 @@ static inline struct aa_profile *aa_get_newest_profile(struct aa_profile *p) return labels_profile(aa_get_newest_label(&p->label)); } -static inline aa_state_t 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 aa_state_t PROFILE_MEDIATES_AF(struct aa_profile *profile, - u16 AF) { - aa_state_t 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 DFA_NOMATCH; - return aa_dfa_match_len(profile->policy.dfa, state, (char *) &be_af, 2); + return aa_dfa_match_len(rules->policy.dfa, state, (char *) &be_af, 2); } /** diff --git a/security/apparmor/ipc.c b/security/apparmor/ipc.c index 4ecaf2ba26c5..dc2fa548312d 100644 --- a/security/apparmor/ipc.c +++ b/security/apparmor/ipc.c @@ -78,19 +78,20 @@ static int profile_signal_perm(struct aa_profile *profile, struct aa_label *peer, u32 request, struct common_audit_data *sa) { + struct aa_ruleset *rules = &profile->rules; struct aa_perms perms; aa_state_t state; if (profile_unconfined(profile) || - !PROFILE_MEDIATES(profile, AA_CLASS_SIGNAL)) + !RULE_MEDIATES(rules, AA_CLASS_SIGNAL)) return 0; aad(sa)->peer = peer; /* TODO: secondary cache check */ - 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); } diff --git a/security/apparmor/label.c b/security/apparmor/label.c index 3a967003fa7c..98dadd960977 100644 --- a/security/apparmor/label.c +++ b/security/apparmor/label.c @@ -1266,20 +1266,21 @@ static inline bool label_is_visible(struct aa_profile *profile, * visibility test. */ 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,6 +1299,7 @@ static inline aa_state_t 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, aa_state_t state, bool subns, u32 request, struct aa_perms *perms) @@ -1309,7 +1311,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 +1325,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; } - *perms = *aa_lookup_perms(&profile->policy, state); + *perms = *aa_lookup_perms(&rules->policy, state); aa_apply_modes_to_perms(profile, perms); if ((perms->allow & request) != request) return -EACCES; @@ -1343,6 +1345,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,6 +1359,7 @@ fail: * check to be stacked. */ static int label_components_match(struct aa_profile *profile, + struct aa_ruleset *rules, struct aa_label *label, aa_state_t start, bool subns, u32 request, struct aa_perms *perms) @@ -1369,7 +1373,7 @@ static int label_components_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, start); + state = match_component(profile, rules, tp, start); if (!state) goto fail; goto next; @@ -1379,16 +1383,16 @@ static int label_components_match(struct aa_profile *profile, return 0; next: - tmp = *aa_lookup_perms(&profile->policy, state); + tmp = *aa_lookup_perms(&rules->policy, state); aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum(perms, &tmp); label_for_each_cont(i, label, tp) { 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; - tmp = *aa_lookup_perms(&profile->policy, state); + tmp = *aa_lookup_perms(&rules->policy, state); aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum(perms, &tmp); } @@ -1406,6 +1410,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 +1419,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, - aa_state_t 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 10e3b11e02ad..ec73e51ca7e3 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -331,16 +331,18 @@ void aa_apply_modes_to_perms(struct aa_profile *profile, struct aa_perms *perms) 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 */ 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); } @@ -355,7 +357,8 @@ int aa_profile_label_perm(struct aa_profile *profile, struct aa_profile *target, aad(sa)->peer = &target->label; aad(sa)->request = request; - aa_profile_match_label(profile, &target->label, type, request, &perms); + aa_profile_match_label(profile, &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 784709286a62..62f2ca32b959 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -166,9 +166,9 @@ static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective, if (COMPLAIN_MODE(profile)) continue; *effective = cap_intersect(*effective, - profile->caps.allow); + profile->rules.caps.allow); *permitted = cap_intersect(*permitted, - profile->caps.allow); + profile->rules.caps.allow); } } rcu_read_unlock(); diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c index 02d8215cb9fd..d4724bdcb07f 100644 --- a/security/apparmor/mount.c +++ b/security/apparmor/mount.c @@ -303,13 +303,14 @@ static int match_mnt_path_str(struct aa_profile *profile, { struct aa_perms perms = { }; const char *mntpnt = NULL, *info = NULL; + struct aa_ruleset *rules = &profile->rules; 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, @@ -324,8 +325,8 @@ static int match_mnt_path_str(struct aa_profile *profile, } error = -EACCES; - pos = do_match_mnt(&profile->policy, - 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]; @@ -363,7 +364,7 @@ static int match_mnt(struct aa_profile *profile, const struct path *path, AA_BUG(!profile); AA_BUG(devpath && !devbuffer); - if (!PROFILE_MEDIATES(profile, AA_CLASS_MOUNT)) + if (!RULE_MEDIATES(&profile->rules, AA_CLASS_MOUNT)) return 0; if (devpath) { @@ -565,6 +566,7 @@ out: static int profile_umount(struct aa_profile *profile, const struct path *path, char *buffer) { + struct aa_ruleset *rules = &profile->rules; struct aa_perms perms = { }; const char *name = NULL, *info = NULL; aa_state_t state; @@ -573,7 +575,7 @@ static int profile_umount(struct aa_profile *profile, const struct path *path, 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, @@ -581,10 +583,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 = *aa_lookup_perms(&profile->policy, state); + perms = *aa_lookup_perms(&rules->policy, state); if (AA_MAY_UMOUNT & ~perms.allow) error = -EACCES; @@ -624,6 +626,7 @@ static struct aa_label *build_pivotroot(struct aa_profile *profile, const struct path *old_path, char *old_buffer) { + struct aa_ruleset *rules = &profile->rules; const char *old_name, *new_name = NULL, *info = NULL; const char *trans_name = NULL; struct aa_perms perms = { }; @@ -635,7 +638,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), @@ -650,12 +653,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 = *aa_lookup_perms(&profile->policy, state); + state = aa_dfa_null_transition(rules->policy.dfa, state); + state = aa_dfa_match(rules->policy.dfa, state, old_name); + perms = *aa_lookup_perms(&rules->policy, state); if (AA_MAY_PIVOTROOT & perms.allow) error = 0; diff --git a/security/apparmor/net.c b/security/apparmor/net.c index d420d3aec3b8..ae789ee834ad 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -108,6 +108,7 @@ 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 = &profile->rules; struct aa_perms perms = { }; aa_state_t state; __be16 buffer[2]; @@ -117,15 +118,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); - perms = *aa_lookup_perms(&profile->policy, state); + 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 +217,26 @@ static int aa_secmark_perm(struct aa_profile *profile, u32 request, u32 secid, { int i, ret; struct aa_perms perms = { }; + struct aa_ruleset *rules = &profile->rules; - 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 3c3a5263695d..74c0a3b34e9b 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -193,6 +193,30 @@ 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); +} + /** * aa_free_profile - free a profile * @profile: the profile to free (MAYBE NULL) @@ -206,7 +230,6 @@ static void aa_free_data(void *ptr, void *arg) void aa_free_profile(struct aa_profile *profile) { struct rhashtable *rht; - int i; AA_DEBUG("%s(%p)\n", __func__, profile); @@ -220,19 +243,10 @@ void aa_free_profile(struct aa_profile *profile) aa_put_ns(profile->ns); kfree_sensitive(profile->rename); - aa_destroy_policydb(&profile->file); - aa_free_cap_rules(&profile->caps); - aa_free_rlimit_rules(&profile->rlimits); - - 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); + free_attachment(&profile->attach); + free_ruleset(&profile->rules); kfree_sensitive(profile->dirname); - aa_destroy_policydb(&profile->xmatch); - aa_destroy_policydb(&profile->policy); + if (profile->data) { rht = profile->data; profile->data = NULL; @@ -544,8 +558,8 @@ name: /* 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); + profile->rules.file.dfa = aa_get_dfa(nulldfa); + profile->rules.policy.dfa = aa_get_dfa(nulldfa); mutex_lock_nested(&profile->ns->lock, profile->ns->level); p = __find_child(&parent->base.profiles, bname); diff --git a/security/apparmor/policy_ns.c b/security/apparmor/policy_ns.c index 43beaad083fe..cb10994cd3b6 100644 --- a/security/apparmor/policy_ns.c +++ b/security/apparmor/policy_ns.c @@ -91,8 +91,8 @@ static struct aa_profile *alloc_unconfined(const char *name) 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); + profile->rules.file.dfa = aa_get_dfa(nulldfa); + profile->rules.policy.dfa = aa_get_dfa(nulldfa); return profile; } diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 5a78aaa0eea4..bbca7772dfa2 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -556,12 +556,12 @@ static bool unpack_xattrs(struct aa_ext *e, struct aa_profile *profile) if (unpack_array(e, NULL, &size) != TRI_TRUE) goto fail; - profile->xattr_count = size; - profile->xattrs = kcalloc(size, sizeof(char *), GFP_KERNEL); - if (!profile->xattrs) + 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 (!unpack_strdup(e, &profile->xattrs[i], NULL)) + if (!unpack_strdup(e, &profile->attach.xattrs[i], NULL)) goto fail; } if (!unpack_nameX(e, AA_ARRAYEND, NULL)) @@ -579,6 +579,7 @@ fail: static bool unpack_secmark(struct aa_ext *e, struct aa_profile *profile) { + struct aa_ruleset *rules = &profile->rules; void *pos = e->pos; u16 size; int i; @@ -587,19 +588,19 @@ static bool unpack_secmark(struct aa_ext *e, struct aa_profile *profile) if (unpack_array(e, NULL, &size) != TRI_TRUE) 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 (!unpack_strdup(e, &profile->secmark[i].label, NULL)) + if (!unpack_strdup(e, &rules->secmark[i].label, NULL)) goto fail; } if (!unpack_nameX(e, AA_ARRAYEND, NULL)) @@ -611,12 +612,12 @@ 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; @@ -634,7 +635,7 @@ static bool unpack_rlimits(struct aa_ext *e, struct aa_profile *profile) u32 tmp = 0; if (!unpack_u32(e, &tmp, NULL)) goto fail; - profile->rlimits.mask = tmp; + profile->rules.rlimits.mask = tmp; if (unpack_array(e, NULL, &size) != TRI_TRUE || size > RLIM_NLIMITS) @@ -644,7 +645,7 @@ static bool unpack_rlimits(struct aa_ext *e, struct aa_profile *profile) int a = aa_map_resource(i); if (!unpack_u64(e, &tmp2, NULL)) goto fail; - profile->rlimits.limits[a].rlim_max = tmp2; + profile->rules.rlimits.limits[a].rlim_max = tmp2; } if (!unpack_nameX(e, AA_ARRAYEND, NULL)) goto fail; @@ -816,6 +817,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"; @@ -850,27 +852,30 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) profile = aa_alloc_profile(name, NULL, GFP_KERNEL); if (!profile) return ERR_PTR(-ENOMEM); + rules = &profile->rules; /* profile renaming is optional */ (void) unpack_str(e, &profile->rename, "rename"); /* attachment string is optional */ - (void) unpack_str(e, &profile->attach, "attach"); + (void) unpack_str(e, &profile->attach.xmatch_str, "attach"); /* xmatch is optional and may be NULL */ - error = unpack_pdb(e, &profile->xmatch, false, false, &info); - if (error) + error = unpack_pdb(e, &profile->attach.xmatch, false, false, &info); + if (error) { + info = "bad xmatch"; goto fail; + } /* neither xmatch_len not xmatch_perms are optional if xmatch is set */ - if (profile->xmatch.dfa) { + if (profile->attach.xmatch.dfa) { if (!unpack_u32(e, &tmp, NULL)) { info = "missing xmatch len"; goto fail; } - profile->xmatch_len = tmp; - profile->xmatch.start[AA_CLASS_XMATCH] = DFA_START; - if (aa_compat_map_xmatch(&profile->xmatch)) { + profile->attach.xmatch_len = tmp; + profile->attach.xmatch.start[AA_CLASS_XMATCH] = DFA_START; + if (aa_compat_map_xmatch(&profile->attach.xmatch)) { info = "failed to convert xmatch permission table"; goto fail; } @@ -926,11 +931,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 (!unpack_u32(e, &(profile->caps.allow.cap[0]), NULL)) + if (!unpack_u32(e, &(rules->caps.allow.cap[0]), NULL)) goto fail; - if (!unpack_u32(e, &(profile->caps.audit.cap[0]), NULL)) + if (!unpack_u32(e, &(rules->caps.audit.cap[0]), NULL)) goto fail; - if (!unpack_u32(e, &(profile->caps.quiet.cap[0]), NULL)) + if (!unpack_u32(e, &(rules->caps.quiet.cap[0]), NULL)) goto fail; if (!unpack_u32(e, &tmpcap.cap[0], NULL)) goto fail; @@ -938,11 +943,11 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) info = "failed to unpack upper profile capabilities"; if (unpack_nameX(e, AA_STRUCT, "caps64")) { /* optional upper half of 64 bit caps */ - if (!unpack_u32(e, &(profile->caps.allow.cap[1]), NULL)) + if (!unpack_u32(e, &(rules->caps.allow.cap[1]), NULL)) goto fail; - if (!unpack_u32(e, &(profile->caps.audit.cap[1]), NULL)) + if (!unpack_u32(e, &(rules->caps.audit.cap[1]), NULL)) goto fail; - if (!unpack_u32(e, &(profile->caps.quiet.cap[1]), NULL)) + if (!unpack_u32(e, &(rules->caps.quiet.cap[1]), NULL)) goto fail; if (!unpack_u32(e, &(tmpcap.cap[1]), NULL)) goto fail; @@ -953,9 +958,9 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) info = "failed to unpack extended profile capabilities"; if (unpack_nameX(e, AA_STRUCT, "capsx")) { /* optional extended caps mediation mask */ - if (!unpack_u32(e, &(profile->caps.extended.cap[0]), NULL)) + if (!unpack_u32(e, &(rules->caps.extended.cap[0]), NULL)) goto fail; - if (!unpack_u32(e, &(profile->caps.extended.cap[1]), NULL)) + if (!unpack_u32(e, &(rules->caps.extended.cap[1]), NULL)) goto fail; if (!unpack_nameX(e, AA_STRUCTEND, NULL)) goto fail; @@ -979,40 +984,41 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) if (unpack_nameX(e, AA_STRUCT, "policydb")) { /* generic policy dfa - optional and may be NULL */ info = "failed to unpack policydb"; - error = unpack_pdb(e, &profile->policy, true, false, &info); + error = unpack_pdb(e, &rules->policy, true, false, + &info); if (error) goto fail; /* Fixup: drop when we get rid of start array */ - if (aa_dfa_next(profile->policy.dfa, profile->policy.start[0], + if (aa_dfa_next(rules->policy.dfa, rules->policy.start[0], AA_CLASS_FILE)) - profile->policy.start[AA_CLASS_FILE] = - aa_dfa_next(profile->policy.dfa, - profile->policy.start[0], + rules->policy.start[AA_CLASS_FILE] = + aa_dfa_next(rules->policy.dfa, + rules->policy.start[0], AA_CLASS_FILE); if (!unpack_nameX(e, AA_STRUCTEND, NULL)) goto fail; - if (aa_compat_map_policy(&profile->policy, e->version)) { + if (aa_compat_map_policy(&rules->policy, e->version)) { 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 */ - error = unpack_pdb(e, &profile->file, false, true, &info); + error = unpack_pdb(e, &rules->file, false, true, &info); if (error) { goto fail; - } else if (profile->file.dfa) { - if (aa_compat_map_file(&profile->file)) { + } else if (rules->file.dfa) { + if (aa_compat_map_file(&rules->file)) { info = "failed to remap file permission table"; goto fail; } - } else if (profile->policy.dfa && - profile->policy.start[AA_CLASS_FILE]) { - profile->file.dfa = aa_get_dfa(profile->policy.dfa); - profile->file.start[AA_CLASS_FILE] = profile->policy.start[AA_CLASS_FILE]; + } 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); + rules->file.dfa = aa_get_dfa(nulldfa); if (unpack_nameX(e, AA_STRUCT, "data")) { info = "out of memory"; @@ -1202,28 +1208,28 @@ static bool verify_perms(struct aa_policydb *pdb) */ static int verify_profile(struct aa_profile *profile) { - if ((profile->file.dfa && - !verify_dfa_xindex(profile->file.dfa, - profile->file.trans.size)) || - (profile->policy.dfa && - !verify_dfa_xindex(profile->policy.dfa, - profile->policy.trans.size))) { + if ((profile->rules.file.dfa && + !verify_dfa_xindex(profile->rules.file.dfa, + profile->rules.file.trans.size)) || + (profile->rules.policy.dfa && + !verify_dfa_xindex(profile->rules.policy.dfa, + profile->rules.policy.trans.size))) { audit_iface(profile, NULL, NULL, "Unpack: Invalid named transition", NULL, -EPROTO); return -EPROTO; } - if (!verify_perms(&profile->file)) { + if (!verify_perms(&profile->rules.file)) { audit_iface(profile, NULL, NULL, "Unpack: Invalid perm index", NULL, -EPROTO); return -EPROTO; } - if (!verify_perms(&profile->policy)) { + if (!verify_perms(&profile->rules.policy)) { audit_iface(profile, NULL, NULL, "Unpack: Invalid perm index", NULL, -EPROTO); return -EPROTO; } - if (!verify_perms(&profile->xmatch)) { + if (!verify_perms(&profile->attach.xmatch)) { audit_iface(profile, NULL, NULL, "Unpack: Invalid perm index", NULL, -EPROTO); return -EPROTO; diff --git a/security/apparmor/resource.c b/security/apparmor/resource.c index cc018469e22d..f28026804d13 100644 --- a/security/apparmor/resource.c +++ b/security/apparmor/resource.c @@ -82,10 +82,11 @@ 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 = &profile->rules; 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); @@ -153,12 +154,12 @@ 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) { + if (old->rules.rlimits.mask) { int j; for (j = 0, mask = 1; j < RLIM_NLIMITS; j++, mask <<= 1) { - if (old->rlimits.mask & mask) { + if (old->rules.rlimits.mask & mask) { rlim = current->signal->rlim + j; initrlim = init_task.signal->rlim + j; rlim->rlim_cur = min(rlim->rlim_max, @@ -172,15 +173,15 @@ void __aa_transition_rlimits(struct aa_label *old_l, struct aa_label *new_l) label_for_each_confined(i, new_l, new) { int j; - if (!new->rlimits.mask) + if (!new->rules.rlimits.mask) continue; for (j = 0, mask = 1; j < RLIM_NLIMITS; j++, mask <<= 1) { - if (!(new->rlimits.mask & mask)) + if (!(new->rules.rlimits.mask & mask)) continue; rlim = current->signal->rlim + j; rlim->rlim_max = min(rlim->rlim_max, - new->rlimits.limits[j].rlim_max); + new->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 b19900f85c14..7e64fba42ca3 100644 --- a/security/apparmor/task.c +++ b/security/apparmor/task.c @@ -223,7 +223,7 @@ 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, @@ -232,8 +232,8 @@ static int profile_ptrace_perm(struct aa_profile *profile, struct aa_perms perms = { }; aad(sa)->peer = peer; - aa_profile_match_label(profile, peer, AA_CLASS_PTRACE, request, - &perms); + aa_profile_match_label(profile, &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 +243,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)) + !RULE_MEDIATES(&tracee->rules, AA_CLASS_PTRACE)) return 0; return profile_ptrace_perm(tracee, tracer, request, sa); @@ -256,7 +256,7 @@ static int profile_tracer_perm(struct aa_profile *tracer, if (profile_unconfined(tracer)) return 0; - if (PROFILE_MEDIATES(tracer, AA_CLASS_PTRACE)) + if (RULE_MEDIATES(&tracer->rules, AA_CLASS_PTRACE)) return profile_ptrace_perm(tracer, tracee, request, sa); /* profile uses the old style capability check for ptrace */ From 1ad22fcc4d0d2fb2e0f35aed555a86d016d5e590 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 5 Sep 2022 20:47:36 -0700 Subject: [PATCH 37/64] apparmor: rework profile->rules to be a list Convert profile->rules to a list as the next step towards supporting multiple rulesets in a profile. For this step only support a single list entry item. The logic for iterating the list will come as a separate step. Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 3 ++- security/apparmor/capability.c | 6 +++-- security/apparmor/domain.c | 24 ++++++++++++------- security/apparmor/file.c | 6 +++-- security/apparmor/include/policy.h | 17 +++++++++++++- security/apparmor/ipc.c | 5 ++-- security/apparmor/lib.c | 6 +++-- security/apparmor/lsm.c | 7 ++++-- security/apparmor/mount.c | 13 +++++++---- security/apparmor/net.c | 6 +++-- security/apparmor/policy.c | 37 +++++++++++++++++++++++++++--- security/apparmor/policy_ns.c | 6 +++-- security/apparmor/policy_unpack.c | 34 ++++++++++++++------------- security/apparmor/resource.c | 19 ++++++++++----- security/apparmor/task.c | 10 ++++---- 15 files changed, 142 insertions(+), 57 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 84ef8b400b40..f6d83ffde3c4 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -611,7 +611,8 @@ 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 = &profile->rules; + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); struct aa_perms tmp = { }; aa_state_t state = DFA_NOMATCH; diff --git a/security/apparmor/capability.c b/security/apparmor/capability.c index b66ec63e2a48..326a51838ef2 100644 --- a/security/apparmor/capability.c +++ b/security/apparmor/capability.c @@ -64,7 +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 = &profile->rules; + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); struct audit_cache *ent; int type = AUDIT_APPARMOR_AUTO; @@ -115,7 +116,8 @@ 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 = &profile->rules; + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); int error; if (cap_raised(rules->caps.allow, cap) && diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index ad035d14cfc5..d4b09f061aee 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -81,7 +81,8 @@ 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 = &profile->rules; + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); const char *ns_name; if (stack) @@ -118,7 +119,8 @@ static int label_compound_match(struct aa_profile *profile, aa_state_t state, bool subns, u32 request, struct aa_perms *perms) { - struct aa_ruleset *rules = &profile->rules; + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); struct aa_profile *tp; struct label_it i; struct path_cond cond = { }; @@ -179,7 +181,8 @@ static int label_components_match(struct aa_profile *profile, aa_state_t start, bool subns, u32 request, struct aa_perms *perms) { - struct aa_ruleset *rules = &profile->rules; + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); struct aa_profile *tp; struct label_it i; struct aa_perms tmp; @@ -503,7 +506,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 = &profile->rules; + 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; @@ -553,7 +557,8 @@ static struct aa_label *x_to_label(struct aa_profile *profile, const char **lookupname, const char **info) { - struct aa_ruleset *rules = &profile->rules; + 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; @@ -620,7 +625,8 @@ static struct aa_label *profile_transition(struct aa_profile *profile, char *buffer, struct path_cond *cond, bool *secure_exec) { - struct aa_ruleset *rules = &profile->rules; + 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; aa_state_t state = rules->file.start[AA_CLASS_FILE]; @@ -719,7 +725,8 @@ static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec, char *buffer, struct path_cond *cond, bool *secure_exec) { - struct aa_ruleset *rules = &profile->rules; + 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"; @@ -1259,7 +1266,8 @@ 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 = &profile->rules; + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); const char *info = NULL; int error = 0; diff --git a/security/apparmor/file.c b/security/apparmor/file.c index ef5d98f81a2b..d7f27848e7cc 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -224,7 +224,8 @@ 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 = &profile->rules; + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); int e = 0; if (profile_unconfined(profile)) @@ -317,7 +318,8 @@ static int profile_path_link(struct aa_profile *profile, const struct path *target, char *buffer2, struct path_cond *cond) { - struct aa_ruleset *rules = &profile->rules; + 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; diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index 9ee2c05e2895..5cadfb20df29 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -124,6 +124,7 @@ struct aa_data { }; /* 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 @@ -133,6 +134,8 @@ struct aa_data { * @secmark: secmark label match info */ struct aa_ruleset { + struct list_head list; + int size; /* TODO: merge policy and file */ @@ -147,6 +150,7 @@ struct aa_ruleset { }; /* 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 @@ -204,7 +208,7 @@ struct aa_profile { const char *disconnected; struct aa_attachment attach; - struct aa_ruleset rules; + struct list_head rules; struct aa_loaddata *rawdata; unsigned char *hash; @@ -227,6 +231,7 @@ 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, @@ -285,6 +290,16 @@ static inline aa_state_t RULE_MEDIATES_AF(struct aa_ruleset *rules, u16 AF) 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); +} + /** * aa_get_profile - increment refcount on profile @p * @p: profile (MAYBE NULL) diff --git a/security/apparmor/ipc.c b/security/apparmor/ipc.c index dc2fa548312d..1d4099385bdf 100644 --- a/security/apparmor/ipc.c +++ b/security/apparmor/ipc.c @@ -78,12 +78,13 @@ static int profile_signal_perm(struct aa_profile *profile, struct aa_label *peer, u32 request, struct common_audit_data *sa) { - struct aa_ruleset *rules = &profile->rules; + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); struct aa_perms perms; aa_state_t state; if (profile_unconfined(profile) || - !RULE_MEDIATES(rules, AA_CLASS_SIGNAL)) + !ANY_RULE_MEDIATES(&profile->rules, AA_CLASS_SIGNAL)) return 0; aad(sa)->peer = peer; diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index ec73e51ca7e3..a630c951bb3b 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -351,14 +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, &profile->rules, &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 62f2ca32b959..a22e53e44123 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -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->rules.caps.allow); + rules->caps.allow); *permitted = cap_intersect(*permitted, - profile->rules.caps.allow); + rules->caps.allow); } } rcu_read_unlock(); diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c index d4724bdcb07f..cdfa430ae216 100644 --- a/security/apparmor/mount.c +++ b/security/apparmor/mount.c @@ -303,7 +303,8 @@ static int match_mnt_path_str(struct aa_profile *profile, { struct aa_perms perms = { }; const char *mntpnt = NULL, *info = NULL; - struct aa_ruleset *rules = &profile->rules; + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); int pos, error; AA_BUG(!profile); @@ -359,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 (!RULE_MEDIATES(&profile->rules, AA_CLASS_MOUNT)) + if (!RULE_MEDIATES(rules, AA_CLASS_MOUNT)) return 0; if (devpath) { @@ -566,7 +569,8 @@ out: static int profile_umount(struct aa_profile *profile, const struct path *path, char *buffer) { - struct aa_ruleset *rules = &profile->rules; + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); struct aa_perms perms = { }; const char *name = NULL, *info = NULL; aa_state_t state; @@ -626,7 +630,8 @@ static struct aa_label *build_pivotroot(struct aa_profile *profile, const struct path *old_path, char *old_buffer) { - struct aa_ruleset *rules = &profile->rules; + 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 = { }; diff --git a/security/apparmor/net.c b/security/apparmor/net.c index ae789ee834ad..788be1609a86 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -108,7 +108,8 @@ 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 = &profile->rules; + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); struct aa_perms perms = { }; aa_state_t state; __be16 buffer[2]; @@ -217,7 +218,8 @@ static int aa_secmark_perm(struct aa_profile *profile, u32 request, u32 secid, { int i, ret; struct aa_perms perms = { }; - struct aa_ruleset *rules = &profile->rules; + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); if (rules->secmark_count == 0) return 0; diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 74c0a3b34e9b..6f4cc8bfe03d 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -217,6 +217,17 @@ static void free_ruleset(struct aa_ruleset *rules) kfree_sensitive(rules->secmark); } +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) @@ -229,6 +240,7 @@ static void free_ruleset(struct aa_ruleset *rules) */ void aa_free_profile(struct aa_profile *profile) { + struct aa_ruleset *rule, *tmp; struct rhashtable *rht; AA_DEBUG("%s(%p)\n", __func__, profile); @@ -244,7 +256,15 @@ void aa_free_profile(struct aa_profile *profile) kfree_sensitive(profile->rename); free_attachment(&profile->attach); - free_ruleset(&profile->rules); + + /* + * 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); if (profile->data) { @@ -272,6 +292,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); @@ -283,6 +304,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); @@ -516,6 +545,7 @@ struct aa_profile *aa_fqlookupn_profile(struct aa_label *base, struct aa_profile *aa_new_null_profile(struct aa_profile *parent, bool hat, const char *base, gfp_t gfp) { + struct aa_ruleset *rules; struct aa_profile *p, *profile; const char *bname; char *name = NULL; @@ -558,8 +588,9 @@ name: /* released on free_profile */ rcu_assign_pointer(profile->parent, aa_get_profile(parent)); profile->ns = aa_get_ns(parent->ns); - profile->rules.file.dfa = aa_get_dfa(nulldfa); - profile->rules.policy.dfa = aa_get_dfa(nulldfa); + rules = list_first_entry(&profile->rules, typeof(*rules), list); + rules->file.dfa = aa_get_dfa(nulldfa); + rules->policy.dfa = aa_get_dfa(nulldfa); mutex_lock_nested(&profile->ns->lock, profile->ns->level); p = __find_child(&parent->base.profiles, bname); diff --git a/security/apparmor/policy_ns.c b/security/apparmor/policy_ns.c index cb10994cd3b6..121aa79bccaa 100644 --- a/security/apparmor/policy_ns.c +++ b/security/apparmor/policy_ns.c @@ -83,6 +83,7 @@ const char *aa_ns_name(struct aa_ns *curr, struct aa_ns *view, bool subns) static struct aa_profile *alloc_unconfined(const char *name) { struct aa_profile *profile; + struct aa_ruleset *rules; profile = aa_alloc_profile(name, NULL, GFP_KERNEL); if (!profile) @@ -91,8 +92,9 @@ static struct aa_profile *alloc_unconfined(const char *name) profile->label.flags |= FLAG_IX_ON_NAME_ERROR | FLAG_IMMUTIBLE | FLAG_NS_COUNT | FLAG_UNCONFINED; profile->mode = APPARMOR_UNCONFINED; - profile->rules.file.dfa = aa_get_dfa(nulldfa); - profile->rules.policy.dfa = aa_get_dfa(nulldfa); + rules = list_first_entry(&profile->rules, typeof(*rules), list); + rules->file.dfa = aa_get_dfa(nulldfa); + rules->policy.dfa = aa_get_dfa(nulldfa); return profile; } diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index bbca7772dfa2..ac9955ef5d4a 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -577,9 +577,8 @@ 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) { - struct aa_ruleset *rules = &profile->rules; void *pos = e->pos; u16 size; int i; @@ -624,7 +623,7 @@ fail: 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; @@ -635,7 +634,7 @@ static bool unpack_rlimits(struct aa_ext *e, struct aa_profile *profile) u32 tmp = 0; if (!unpack_u32(e, &tmp, NULL)) goto fail; - profile->rules.rlimits.mask = tmp; + rules->rlimits.mask = tmp; if (unpack_array(e, NULL, &size) != TRI_TRUE || size > RLIM_NLIMITS) @@ -645,7 +644,7 @@ static bool unpack_rlimits(struct aa_ext *e, struct aa_profile *profile) int a = aa_map_resource(i); if (!unpack_u64(e, &tmp2, NULL)) goto fail; - profile->rules.rlimits.limits[a].rlim_max = tmp2; + rules->rlimits.limits[a].rlim_max = tmp2; } if (!unpack_nameX(e, AA_ARRAYEND, NULL)) goto fail; @@ -852,7 +851,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) profile = aa_alloc_profile(name, NULL, GFP_KERNEL); if (!profile) return ERR_PTR(-ENOMEM); - rules = &profile->rules; + rules = list_first_entry(&profile->rules, typeof(*rules), list); /* profile renaming is optional */ (void) unpack_str(e, &profile->rename, "rename"); @@ -971,12 +970,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; } @@ -1208,23 +1207,26 @@ static bool verify_perms(struct aa_policydb *pdb) */ static int verify_profile(struct aa_profile *profile) { - if ((profile->rules.file.dfa && - !verify_dfa_xindex(profile->rules.file.dfa, - profile->rules.file.trans.size)) || - (profile->rules.policy.dfa && - !verify_dfa_xindex(profile->rules.policy.dfa, - profile->rules.policy.trans.size))) { + 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(&profile->rules.file)) { + if (!verify_perms(&rules->file)) { audit_iface(profile, NULL, NULL, "Unpack: Invalid perm index", NULL, -EPROTO); return -EPROTO; } - if (!verify_perms(&profile->rules.policy)) { + if (!verify_perms(&rules->policy)) { audit_iface(profile, NULL, NULL, "Unpack: Invalid perm index", NULL, -EPROTO); return -EPROTO; diff --git a/security/apparmor/resource.c b/security/apparmor/resource.c index f28026804d13..ed543f4edfd9 100644 --- a/security/apparmor/resource.c +++ b/security/apparmor/resource.c @@ -82,7 +82,8 @@ 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 = &profile->rules; + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); int e = 0; if (rules->rlimits.mask & (1 << resource) && new_rlim->rlim_max > @@ -154,12 +155,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->rules.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->rules.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, @@ -171,17 +175,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->rules.rlimits.mask) + if (!rules->rlimits.mask) continue; for (j = 0, mask = 1; j < RLIM_NLIMITS; j++, mask <<= 1) { - if (!(new->rules.rlimits.mask & mask)) + if (!(rules->rlimits.mask & mask)) continue; rlim = current->signal->rlim + j; rlim->rlim_max = min(rlim->rlim_max, - new->rules.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 7e64fba42ca3..5000cbd055b6 100644 --- a/security/apparmor/task.c +++ b/security/apparmor/task.c @@ -229,11 +229,13 @@ 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, &profile->rules, peer, - AA_CLASS_PTRACE, request, &perms); + 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) || - !RULE_MEDIATES(&tracee->rules, 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 (RULE_MEDIATES(&tracer->rules, 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 */ From 961f3e3de14467f3babe252f7b6cc44a36ebba64 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sun, 11 Sep 2022 22:05:26 -0700 Subject: [PATCH 38/64] apparmor: fix aa_class_names[] to match reserved classes The class name map did not have the reserved names added. Fix this Signed-off-by: John Johansen --- security/apparmor/audit.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c index e638f7bc9f52..8dfdda98fbf1 100644 --- a/security/apparmor/audit.c +++ b/security/apparmor/audit.c @@ -48,13 +48,28 @@ static const char *const aa_class_names[] = { "unknown", "ptrace", "signal", - "unknown", + "xmatch", "unknown", "unknown", "net", "unknown", "label", + "posix_mqueue", + "io_uring", + "module", "lsm", + "unknown", + "unknown", + "unknown", + "unknown", + "unknown", + "unknown", + "unknown", + "unknown", + "unknown", + "unknown", + "X", + "dbus", }; From 1f939c6bd1512d0b39b470396740added3cb403f Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 20 Sep 2022 04:01:28 -0700 Subject: [PATCH 39/64] apparmor: Fix regression in stacking due to label flags The unconfined label flag is not being computed correctly. It should only be set if all the profiles in the vector are set, which is different than what is required for the debug and stale flag that are set if any on the profile flags are set. Fixes: c1ed5da19765 ("apparmor: allow label to carry debug flags") Signed-off-by: John Johansen --- security/apparmor/label.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/security/apparmor/label.c b/security/apparmor/label.c index 98dadd960977..aa4031628af5 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); From adaa9a3f72e6f98538bfac54f6dc4afc0537f410 Mon Sep 17 00:00:00 2001 From: Gaosheng Cui Date: Fri, 23 Sep 2022 17:21:18 +0800 Subject: [PATCH 40/64] apparmor: Simplify obtain the newest label on a cred In aa_get_task_label(), aa_get_newest_cred_label(__task_cred(task)) can do the same things as aa_get_newest_label(__aa_task_raw_label(task)), so we can replace it and remove __aa_task_raw_label() to simplify the code. Signed-off-by: Gaosheng Cui Signed-off-by: John Johansen --- security/apparmor/include/cred.h | 13 ------------- security/apparmor/task.c | 2 +- 2 files changed, 1 insertion(+), 14 deletions(-) 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 @@ -63,19 +63,6 @@ static inline struct aa_label *aa_get_newest_cred_label(const struct cred *cred) return aa_get_newest_label(aa_cred_raw_label(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 * diff --git a/security/apparmor/task.c b/security/apparmor/task.c index 5000cbd055b6..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; From 65f7f666f21ce374628d58b3cc48515070f31e72 Mon Sep 17 00:00:00 2001 From: Xiu Jianfeng Date: Wed, 14 Sep 2022 15:46:07 +0800 Subject: [PATCH 41/64] apparmor: make __aa_path_perm() static Make __aa_path_perm() static as it's only used inside apparmor/file.c. Signed-off-by: Xiu Jianfeng Signed-off-by: John Johansen --- security/apparmor/file.c | 7 ++++--- security/apparmor/include/file.h | 3 --- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/security/apparmor/file.c b/security/apparmor/file.c index d7f27848e7cc..e7dc5ea38997 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -220,9 +220,10 @@ aa_state_t aa_str_perms(struct aa_policydb *file_rules, aa_state_t start, 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); diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index 1a1c0f0c5071..5be620af33ba 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -119,9 +119,6 @@ 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); From 1ddece8cd0f43582085497eacff2e3cd37f93d1f Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sat, 24 Sep 2022 22:25:25 -0700 Subject: [PATCH 42/64] apparmor: Fix doc comment for compute_fperms When compute_fperms was moved to policy_compat and made static it was renamed from aa_compute_fperms to just compute_fperms to help indicate it is only available statically. Unfortunately the doc comment did not also get updated to reflect the change. Reported-by: kernel test robot Signed-off-by: John Johansen --- security/apparmor/policy_compat.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/security/apparmor/policy_compat.c b/security/apparmor/policy_compat.c index 1aa5cced935e..9e52e218bf30 100644 --- a/security/apparmor/policy_compat.c +++ b/security/apparmor/policy_compat.c @@ -140,8 +140,8 @@ static struct aa_perms compute_fperms_other(struct aa_dfa *dfa, } /** - * aa_compute_fperms - convert dfa compressed perms to internal perms and store - * them so they can be retrieved later. + * 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 From 73c7e91c8bc98a5da94be62a9a4ba2793f86a97b Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sat, 24 Sep 2022 22:34:07 -0700 Subject: [PATCH 43/64] apparmor: Remove unnecessary size check when unpacking trans_table The index into the trans_table has a max size of 2^24 bits which the code was testing but this is unnecessary as unpack_array can only unpack a table of 2^16 bits in size so the table unpacked will never be larger than what can be indexed, and any test here is redundant. Reported-by: kernel test robot Signed-off-by: John Johansen --- security/apparmor/policy_unpack.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index ac9955ef5d4a..6deaeecb76fe 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -484,9 +484,13 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_str_table *strs) u16 size; int i; - if (unpack_array(e, NULL, &size) != TRI_TRUE || - size > (1 << 24)) - /* currently 2^24 bits entries 0-3 */ + if (unpack_array(e, NULL, &size) != TRI_TRUE) + /* + * 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; table = kcalloc(size, sizeof(char *), GFP_KERNEL); if (!table) From 14d37a7f14569adbf7a019710762271fa2a9e739 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sun, 25 Sep 2022 15:36:45 -0700 Subject: [PATCH 44/64] apparmor: make sure the decompression ctx is promperly initialized The decompress ctx was not properly initialized when reading raw profile data back to userspace. Reported-by: kernel test robot Fixes: 52ccc20c652b ("apparmor: use zstd compression for profile data") Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index f6d83ffde3c4..ddd64b8ebf05 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -1327,7 +1327,11 @@ static int decompress_zstd(char *src, size_t slen, char *dst, size_t dlen) ret = -ENOMEM; goto cleanup; } - + 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; From 70f24a9f9084b7fffd95daa707cce8e339b189dd Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 29 Sep 2022 06:24:29 -0700 Subject: [PATCH 45/64] apparmor: Fix undefined references to zstd_ symbols Unfortunately the switch to using zstd compression did not properly ifdef all the code that uses zstd_ symbols. So that if exporting of binary policy is disabled in the config the compile will fail with the following errors security/apparmor/lsm.c:1545: undefined reference to `zstd_min_clevel' aarch64-linux-ld: security/apparmor/lsm.c:1545: undefined reference to `zstd_max_clevel' Reported-by: kernel test robot Fixes: 52ccc20c652b ("apparmor: use zstd compression for profile data") Signed-off-by: John Johansen Acked-by: Jon Tourville --- security/apparmor/apparmorfs.c | 4 ++-- security/apparmor/include/apparmor.h | 11 +++++++++++ security/apparmor/lsm.c | 5 ++--- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index ddd64b8ebf05..2c138309ad66 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -1202,13 +1202,13 @@ static int seq_ns_name_show(struct seq_file *seq, void *v) static int seq_ns_compress_min_show(struct seq_file *seq, void *v) { - seq_printf(seq, "%d\n", zstd_min_clevel()); + 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", zstd_max_clevel()); + seq_printf(seq, "%d\n", AA_MAX_CLEVEL); return 0; } diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h index 6d9ca075fcb9..8a81557c9d59 100644 --- a/security/apparmor/include/apparmor.h +++ b/security/apparmor/include/apparmor.h @@ -51,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/lsm.c b/security/apparmor/lsm.c index a22e53e44123..8e2b951c4988 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -1365,7 +1365,7 @@ module_param_named(export_binary, aa_g_export_binary, aabool, 0600); #endif /* policy loaddata compression level */ -int aa_g_rawdata_compression_level = ZSTD_CLEVEL_DEFAULT; +int aa_g_rawdata_compression_level = AA_DEFAULT_CLEVEL; module_param_named(rawdata_compression_level, aa_g_rawdata_compression_level, aacompressionlevel, 0400); @@ -1547,8 +1547,7 @@ 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, - zstd_min_clevel(), - zstd_max_clevel()); + AA_MIN_CLEVEL, AA_MAX_CLEVEL); pr_info("AppArmor: policy rawdata compression level set to %d\n", aa_g_rawdata_compression_level); From a2f31df06b7aa1769f12ec6f9ae7f18e78582cad Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 29 Sep 2022 06:48:10 -0700 Subject: [PATCH 46/64] apparmor: Fix decompression of rawdata for read back to userspace The rawdata readback has a few of problems. First if compression is enabled when the data is read then the compressed data is read out instead decompressing the data. Second if compression of the data fails, the code does not handle holding onto the raw_data in uncompressed form. Third if the compression is enabled/disabled after the rawdata was loaded, the check against the global control of whether to use compression does not reflect what was already done to the data. Fix these by always storing the compressed size, along with the original data size even if compression fails or is not used. And use this to detect whether the rawdata is actually compressed. Fixes: 52ccc20c652b ("apparmor: use zstd compression for profile data") Signed-off-by: John Johansen Acked-by: Jon Tourville --- security/apparmor/apparmorfs.c | 2 +- security/apparmor/policy_unpack.c | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 2c138309ad66..424b2c1e586d 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -1315,7 +1315,7 @@ SEQ_RAWDATA_FOPS(compressed_size); static int decompress_zstd(char *src, size_t slen, char *dst, size_t dlen) { #ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY - if (aa_g_rawdata_compression_level == 0) { + if (slen < dlen) { const size_t wksp_len = zstd_dctx_workspace_bound(); zstd_dctx *ctx; void *wksp; diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 6deaeecb76fe..45c9dfdc8e0d 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -1294,7 +1294,7 @@ static int compress_zstd(const char *src, size_t slen, char **dst, size_t *dlen) } out_len = zstd_compress_cctx(ctx, out, out_len, src, slen, ¶ms); - if (zstd_is_error(out_len)) { + if (zstd_is_error(out_len) || out_len >= slen) { ret = -EINVAL; goto cleanup; } @@ -1348,9 +1348,10 @@ static int compress_loaddata(struct aa_loaddata *data) void *udata = data->data; int error = compress_zstd(udata, data->size, &data->data, &data->compressed_size); - if (error) + if (error) { + data->compressed_size = data->size; return error; - + } if (udata != data->data) kvfree(udata); } else From 32490541682bf8ea445e9bd29c866981851e0912 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 3 Oct 2022 01:30:38 -0700 Subject: [PATCH 47/64] apparmor: Fix kunit test for out of bounds array The apparmor kunit tests are failing on the out of bounds array check with the following failure # policy_unpack_test_unpack_array_out_of_bounds: EXPECTATION FAILED at security/apparmor/policy_unpack_test.c:178 Expected unpack_array(puf->e, name, &array_size) == 1, but unpack_array(puf->e, name, &array_size) == -1 # policy_unpack_test_unpack_array_out_of_bounds: EXPECTATION FAILED at security/apparmor/policy_unpack_test.c:180 Expected array_size == 0, but array_size == 64192 not ok 5 - policy_unpack_test_unpack_array_out_of_bounds This is because unpack_array changed to allow distinguishing between the array not being present and an error. In the error case the array size is not set and should not be tested. Reported-by: kernel test robot Fixes: 995a5b64620e ("apparmor: make unpack_array return a trianary value") Signed-off-by: John Johansen --- security/apparmor/policy_unpack_test.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/security/apparmor/policy_unpack_test.c b/security/apparmor/policy_unpack_test.c index 1a43d538c4c0..b214f6ea8a72 100644 --- a/security/apparmor/policy_unpack_test.c +++ b/security/apparmor/policy_unpack_test.c @@ -176,8 +176,7 @@ static void policy_unpack_test_unpack_array_out_of_bounds(struct kunit *test) puf->e->end = puf->e->start + TEST_ARRAY_BUF_OFFSET + sizeof(u16); KUNIT_EXPECT_EQ(test, unpack_array(puf->e, name, &array_size), - TRI_TRUE); - KUNIT_EXPECT_EQ(test, array_size, 0); + TRI_FALSE); KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, puf->e->start + TEST_NAMED_ARRAY_BUF_OFFSET); } From 5515a8e30eaa8ae0d57ec59c908716cf2af114ae Mon Sep 17 00:00:00 2001 From: Muhammad Usama Anjum Date: Tue, 4 Oct 2022 13:45:15 +0500 Subject: [PATCH 48/64] apparmor: store return value of unpack_perms_table() to signed variable The unpack_perms_table() can return error which is negative value. Store the return value to a signed variable. policy->size is unsigned variable. It shouldn't be used to store the return status. Fixes: 2d6b2dea7f3c ("apparmor: add the ability for policy to specify a permission table") Signed-off-by: Muhammad Usama Anjum Signed-off-by: John Johansen --- security/apparmor/policy_unpack.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 45c9dfdc8e0d..09f316943951 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -734,14 +734,18 @@ static int unpack_pdb(struct aa_ext *e, struct aa_policydb *policy, { void *pos = e->pos; int i, flags, error = -EPROTO; + ssize_t size; - policy->size = unpack_perms_table(e, &policy->perms); - if (policy->size < 0) { - error = policy->size; + size = unpack_perms_table(e, &policy->perms); + if (size < 0) { + error = size; policy->perms = NULL; *info = "failed to unpack - perms"; goto fail; - } else if (policy->perms) { + } + policy->size = size; + + if (policy->perms) { /* perms table present accept is index */ flags = TO_ACCEPT1_FLAG(YYTD_DATA32); } else { From ee21a175ecfa821b74822881d354c7f848930738 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 10 Oct 2022 11:18:50 -0700 Subject: [PATCH 49/64] apparmor: fix uninitialize table variable in error in unpack_trans_table The error path has one case where *table is uninitialized, initialize it. Fixes: a0792e2ceddc ("apparmor: make transition table unpack generic so it can be reused") Reported-by: kernel test robot Signed-off-by: John Johansen --- security/apparmor/policy_unpack.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 09f316943951..3b956b1235f3 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -477,7 +477,7 @@ static struct aa_dfa *unpack_dfa(struct aa_ext *e, int flags) static bool unpack_trans_table(struct aa_ext *e, struct aa_str_table *strs) { void *saved_pos = e->pos; - char **table; + char **table = NULL; /* exec table is optional */ if (unpack_nameX(e, AA_STRUCT, "xtable")) { From 53991aedcd34760be23f1b0ef312e39b6add84af Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 10 Oct 2022 12:15:10 -0700 Subject: [PATCH 50/64] apparmor: Fix unpack_profile() warn: passing zero to 'ERR_PTR' unpack_profile() sets a default error on entry but this gets overridden by error assignment by functions called in its body. If an error check that was relying on the default value is triggered after one of these error assignments then zero will be passed to ERR_PTR. Fix this by setting up a default -EPROTO assignment in the error path and while we are at it make sure the correct error is returned in non-default cases. Fixes: 217af7e2f4de ("apparmor: refactor profile rules and attachments") Reported-by: kernel test robot Signed-off-by: John Johansen --- security/apparmor/policy_unpack.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 3b956b1235f3..2e028d540c6b 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -851,6 +851,7 @@ 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; @@ -882,7 +883,8 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) } profile->attach.xmatch_len = tmp; profile->attach.xmatch.start[AA_CLASS_XMATCH] = DFA_START; - if (aa_compat_map_xmatch(&profile->attach.xmatch)) { + error = aa_compat_map_xmatch(&profile->attach.xmatch); + if (error) { info = "failed to convert xmatch permission table"; goto fail; } @@ -1004,7 +1006,8 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) AA_CLASS_FILE); if (!unpack_nameX(e, AA_STRUCTEND, NULL)) goto fail; - if (aa_compat_map_policy(&rules->policy, e->version)) { + error = aa_compat_map_policy(&rules->policy, e->version); + if (error) { info = "failed to remap policydb permission table"; goto fail; } @@ -1016,7 +1019,8 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) if (error) { goto fail; } else if (rules->file.dfa) { - if (aa_compat_map_file(&rules->file)) { + error = aa_compat_map_file(&rules->file); + if (error) { info = "failed to remap file permission table"; goto fail; } @@ -1027,12 +1031,14 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) } else rules->file.dfa = aa_get_dfa(nulldfa); + error = -EPROTO; if (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); @@ -1049,6 +1055,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; } @@ -1058,6 +1065,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; } @@ -1079,6 +1087,9 @@ 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 (profile) name = NULL; else if (!name) From 2f7a29debae2efef94b981377fa3622986cd57f5 Mon Sep 17 00:00:00 2001 From: Gaosheng Cui Date: Mon, 26 Sep 2022 10:28:39 +0800 Subject: [PATCH 51/64] apparmor: remove useless static inline functions Remove the following useless static inline functions: 1. label_is_visible() is a static function in security/apparmor/label.c, and it's not used, aa_ns_visible() can do the same things as it, so it's redundant. 2. is_deleted() is a static function in security/apparmor/file.c, and it's not used since commit aebd873e8d3e ("apparmor: refactor path name lookup and permission checks around labels"), so it's redundant. They are redundant, so remove them. Signed-off-by: Gaosheng Cui Signed-off-by: John Johansen --- security/apparmor/file.c | 13 ------------- security/apparmor/label.c | 6 ------ 2 files changed, 19 deletions(-) diff --git a/security/apparmor/file.c b/security/apparmor/file.c index e7dc5ea38997..deb73480f0c6 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -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) diff --git a/security/apparmor/label.c b/security/apparmor/label.c index aa4031628af5..8a2af96f4da5 100644 --- a/security/apparmor/label.c +++ b/security/apparmor/label.c @@ -1256,12 +1256,6 @@ 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 From 1f2bc06a8dbff73957f433b22c6fd35fccfb47a4 Mon Sep 17 00:00:00 2001 From: Gaosheng Cui Date: Mon, 26 Sep 2022 19:48:38 +0800 Subject: [PATCH 52/64] apparmor: fix obsoleted comments for aa_getprocattr() and audit_resource() Update the comments for aa_getprocattr() and audit_resource(), the args of them have beed changed since commit 76a1d263aba3 ("apparmor: switch getprocattr to using label_print fns()"). Signed-off-by: Gaosheng Cui Signed-off-by: John Johansen --- security/apparmor/procattr.c | 11 +++++------ security/apparmor/resource.c | 2 ++ 2 files changed, 7 insertions(+), 6 deletions(-) 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 ed543f4edfd9..1b75d8343a8d 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 From 58f89ce58bb4f5cf5963b20a19aaa2431b0412d8 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 3 Oct 2022 02:48:24 -0700 Subject: [PATCH 53/64] apparmor: refactor code that alloc null profiles Bother unconfined and learning profiles use the null profile as their base. Refactor so they are share a common base routine. This doesn't save much atm but will be important when the feature set of the parent is inherited. Signed-off-by: John Johansen --- security/apparmor/domain.c | 12 ++++---- security/apparmor/include/policy.h | 6 ++-- security/apparmor/policy.c | 47 ++++++++++++++++++++---------- security/apparmor/policy_ns.c | 6 +--- 4 files changed, 43 insertions(+), 28 deletions(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index d4b09f061aee..b447bc13ea8e 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -681,8 +681,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"; @@ -1009,8 +1009,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; @@ -1361,8 +1361,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/include/policy.h b/security/apparmor/include/policy.h index 5cadfb20df29..545f791cabda 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -234,8 +234,10 @@ 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); diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 6f4cc8bfe03d..c17ccedd35f1 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -524,8 +524,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 @@ -542,10 +570,9 @@ 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_ruleset *rules; struct aa_profile *p, *profile; const char *bname; char *name = NULL; @@ -575,22 +602,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); - rules = list_first_entry(&profile->rules, typeof(*rules), list); - rules->file.dfa = aa_get_dfa(nulldfa); - rules->policy.dfa = aa_get_dfa(nulldfa); mutex_lock_nested(&profile->ns->lock, profile->ns->level); p = __find_child(&parent->base.profiles, bname); diff --git a/security/apparmor/policy_ns.c b/security/apparmor/policy_ns.c index 121aa79bccaa..5c38563a6dcf 100644 --- a/security/apparmor/policy_ns.c +++ b/security/apparmor/policy_ns.c @@ -83,18 +83,14 @@ const char *aa_ns_name(struct aa_ns *curr, struct aa_ns *view, bool subns) static struct aa_profile *alloc_unconfined(const char *name) { struct aa_profile *profile; - struct aa_ruleset *rules; - 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; - rules = list_first_entry(&profile->rules, typeof(*rules), list); - rules->file.dfa = aa_get_dfa(nulldfa); - rules->policy.dfa = aa_get_dfa(nulldfa); return profile; } From 665b1856dc2399828d8ee07a18d4fd79868e729a Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 3 Oct 2022 06:06:26 -0700 Subject: [PATCH 54/64] apparmor: Fix loading of child before parent Unfortunately it is possible for some userspace's to load children profiles before the parent profile. This can even happen when the child and the parent are in different load sets. Fix this by creating a null place holder profile that grants no permissions and can be replaced by the parent once it is loaded. Signed-off-by: John Johansen --- security/apparmor/policy.c | 87 ++++++++++++++++++++++++++++++++++---- 1 file changed, 78 insertions(+), 9 deletions(-) diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index c17ccedd35f1..66034cf96f4c 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -423,6 +423,57 @@ static struct aa_policy *__lookup_parent(struct aa_ns *ns, return &profile->base; } +/** + * __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) @@ -1032,6 +1083,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); @@ -1056,21 +1108,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 */ From 64a27ba984342d6c5cf5facc278de5c5df1fd3ff Mon Sep 17 00:00:00 2001 From: Jiapeng Chong Date: Sat, 8 Oct 2022 14:34:09 +0800 Subject: [PATCH 55/64] AppArmor: Fix kernel-doc security/apparmor/audit.c:93: warning: expecting prototype for audit_base(). Prototype was for audit_pre() instead. Link: https://bugzilla.openanolis.cn/show_bug.cgi?id=2339 Reported-by: Abaci Robot Signed-off-by: Jiapeng Chong Signed-off-by: John Johansen --- security/apparmor/audit.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c index 8dfdda98fbf1..5a7978aa4b19 100644 --- a/security/apparmor/audit.c +++ b/security/apparmor/audit.c @@ -83,7 +83,7 @@ static const char *const aa_class_names[] = { */ /** - * 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) * From 391f121150a5191c932e02775b6e29e59a3f5a94 Mon Sep 17 00:00:00 2001 From: Jiapeng Chong Date: Sat, 8 Oct 2022 14:34:10 +0800 Subject: [PATCH 56/64] LSM: Fix kernel-doc security/apparmor/lsm.c:753: warning: expecting prototype for apparmor_bprm_committed_cred(). Prototype was for apparmor_bprm_committed_creds() instead. Link: https://bugzilla.openanolis.cn/show_bug.cgi?id=2338 Reported-by: Abaci Robot Signed-off-by: Jiapeng Chong Signed-off-by: John Johansen --- security/apparmor/lsm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 8e2b951c4988..ca4d190a737d 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -741,7 +741,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) From a2217387c3ec09117b3b6eaa5ec8a0d7d347d4ba Mon Sep 17 00:00:00 2001 From: Jiapeng Chong Date: Sat, 8 Oct 2022 14:34:11 +0800 Subject: [PATCH 57/64] AppArmor: Fix kernel-doc security/apparmor/ipc.c:53: warning: expecting prototype for audit_cb(). Prototype was for audit_signal_cb() instead. Link: https://bugzilla.openanolis.cn/show_bug.cgi?id=2337 Reported-by: Abaci Robot Signed-off-by: Jiapeng Chong Signed-off-by: John Johansen --- security/apparmor/ipc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/ipc.c b/security/apparmor/ipc.c index 1d4099385bdf..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) */ From 37923d4321b1e38170086da2c117f78f2b0f49c6 Mon Sep 17 00:00:00 2001 From: Xiu Jianfeng Date: Fri, 21 Oct 2022 08:46:04 +0800 Subject: [PATCH 58/64] apparmor: Use pointer to struct aa_label for lbs_cred According to the implementations of cred_label() and set_cred_label(), we should use pointer to struct aa_label for lbs_cred instead of struct aa_task_ctx, this patch fixes it. Fixes: bbd3662a8348 ("Infrastructure management of the cred security blob") Signed-off-by: Xiu Jianfeng Signed-off-by: John Johansen --- security/apparmor/lsm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index ca4d190a737d..25114735bc11 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -1198,10 +1198,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), }; From d44c692350d9a376abab46aa0d70587951971068 Mon Sep 17 00:00:00 2001 From: Yang Li Date: Fri, 14 Oct 2022 16:42:55 +0800 Subject: [PATCH 59/64] apparmor: Fix spelling of function name in comment block 'resouce' -> 'resource' Link: https://bugzilla.openanolis.cn/show_bug.cgi?id=2396 Reported-by: Abaci Robot Signed-off-by: Yang Li Signed-off-by: John Johansen --- security/apparmor/resource.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/resource.c b/security/apparmor/resource.c index 1b75d8343a8d..e85948164896 100644 --- a/security/apparmor/resource.c +++ b/security/apparmor/resource.c @@ -68,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. From 7dd426e33e2f9275ac03a306efdc89aa86515a52 Mon Sep 17 00:00:00 2001 From: Gaosheng Cui Date: Tue, 25 Oct 2022 11:59:30 +0800 Subject: [PATCH 60/64] apparmor: fix a memleak in free_ruleset() When the aa_profile is released, we will call free_ruleset to release aa_ruleset, but we don't free the memory of aa_ruleset, so there will be memleak, fix it. unreferenced object 0xffff8881475df800 (size 1024): comm "apparmor_parser", pid 883, jiffies 4294899650 (age 9114.088s) hex dump (first 32 bytes): 00 f8 5d 47 81 88 ff ff 00 f8 5d 47 81 88 ff ff ..]G......]G.... 00 00 00 00 00 00 00 00 00 dc 65 47 81 88 ff ff ..........eG.... backtrace: [<00000000370e658e>] __kmem_cache_alloc_node+0x182/0x700 [<00000000f2f5a6d2>] kmalloc_trace+0x2c/0x130 [<00000000c5c905b3>] aa_alloc_profile+0x1bc/0x5c0 [<00000000bc4fa72b>] unpack_profile+0x319/0x30c0 [<00000000eab791e9>] aa_unpack+0x307/0x1450 [<000000002c3a6ee1>] aa_replace_profiles+0x1b8/0x3790 [<00000000d0c3fd54>] policy_update+0x35a/0x890 [<00000000d04fed90>] profile_replace+0x1d1/0x260 [<00000000cba0c0a7>] vfs_write+0x283/0xd10 [<000000006bae64a5>] ksys_write+0x134/0x260 [<00000000b2fd8f31>] __x64_sys_write+0x78/0xb0 [<00000000f3c8a015>] do_syscall_64+0x5c/0x90 [<00000000a242b1db>] entry_SYSCALL_64_after_hwframe+0x63/0xcd Fixes: 217af7e2f4de ("apparmor: refactor profile rules and attachments") Signed-off-by: Gaosheng Cui Signed-off-by: John Johansen --- security/apparmor/policy.c | 1 + 1 file changed, 1 insertion(+) diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 66034cf96f4c..51e8184e0fec 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -215,6 +215,7 @@ static void free_ruleset(struct aa_ruleset *rules) 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) From 3265949f7cd36a724a35020202c618094be1cf28 Mon Sep 17 00:00:00 2001 From: Xiu Jianfeng Date: Fri, 21 Oct 2022 17:36:02 +0800 Subject: [PATCH 61/64] apparmor: Fix memleak issue in unpack_profile() Before aa_alloc_profile(), it has allocated string for @*ns_name if @tmpns is not NULL, so directly return -ENOMEM if aa_alloc_profile() failed will cause a memleak issue, and even if aa_alloc_profile() succeed, in the @fail_profile tag of aa_unpack(), it need to free @ns_name as well, this patch fixes them. Fixes: 736ec752d95e ("AppArmor: policy routines for loading and unpacking policy") Fixes: 04dc715e24d0 ("apparmor: audit policy ns specified in policy load") Signed-off-by: Xiu Jianfeng Signed-off-by: John Johansen --- security/apparmor/policy_unpack.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 2e028d540c6b..1bf8cfb8700a 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -858,8 +858,11 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) } 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 */ @@ -1090,6 +1093,10 @@ 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) @@ -1392,6 +1399,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, @@ -1401,7 +1409,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) @@ -1432,6 +1439,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; @@ -1452,6 +1460,7 @@ int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, return 0; fail_profile: + kfree(ns_name); aa_put_profile(profile); fail: From e9e6fa49dbab6d84c676666f3fe7d360497fd65b Mon Sep 17 00:00:00 2001 From: Xiu Jianfeng Date: Fri, 28 Oct 2022 20:33:20 +0800 Subject: [PATCH 62/64] apparmor: Fix memleak in alloc_ns() After changes in commit a1bd627b46d1 ("apparmor: share profile name on replacement"), the hname member of struct aa_policy is not valid slab object, but a subset of that, it can not be freed by kfree_sensitive(), use aa_policy_destroy() to fix it. Fixes: a1bd627b46d1 ("apparmor: share profile name on replacement") Signed-off-by: Xiu Jianfeng Signed-off-by: John Johansen --- security/apparmor/policy_ns.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/policy_ns.c b/security/apparmor/policy_ns.c index 5c38563a6dcf..fd5b7afbcb48 100644 --- a/security/apparmor/policy_ns.c +++ b/security/apparmor/policy_ns.c @@ -132,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; From f6c64dc32ab91b4c37fa2a255d2270f4ff0b95ba Mon Sep 17 00:00:00 2001 From: Xiu Jianfeng Date: Sat, 29 Oct 2022 09:25:05 +0800 Subject: [PATCH 63/64] apparmor: Add __init annotation to aa_{setup/teardown}_dfa_engine() The aa_setup_dfa_engine() and aa_teardown_dfa_engine() is only called in apparmor_init(), so let us add __init annotation to them. Fixes: 11c236b89d7c ("apparmor: add a default null dfa") Signed-off-by: Xiu Jianfeng Signed-off-by: John Johansen --- security/apparmor/match.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/security/apparmor/match.c b/security/apparmor/match.c index 5095c26ca683..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); From 4295c60bbe9e63e35d330546eeaa1d2b62dae303 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 1 Nov 2022 05:40:40 -0700 Subject: [PATCH 64/64] apparmor: Fix uninitialized symbol 'array_size' in policy_unpack_test.c Make sure array_size is initialized in the kunit test to get rid of compiler warnings. This will also make sure the following tests fail consistently if the first test fails. Reported-by: kernel test robot Signed-off-by: John Johansen --- security/apparmor/policy_unpack_test.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/security/apparmor/policy_unpack_test.c b/security/apparmor/policy_unpack_test.c index b214f6ea8a72..7465da42492d 100644 --- a/security/apparmor/policy_unpack_test.c +++ b/security/apparmor/policy_unpack_test.c @@ -140,7 +140,7 @@ 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; @@ -155,7 +155,7 @@ 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;