| 1 | // SPDX-License-Identifier: GPL-2.0-only |
| 2 | /* |
| 3 | * AppArmor security module |
| 4 | * |
| 5 | * This file contains AppArmor auditing functions |
| 6 | * |
| 7 | * Copyright (C) 1998-2008 Novell/SUSE |
| 8 | * Copyright 2009-2010 Canonical Ltd. |
| 9 | */ |
| 10 | |
| 11 | #include <linux/audit.h> |
| 12 | #include <linux/socket.h> |
| 13 | |
| 14 | #include "include/apparmor.h" |
| 15 | #include "include/audit.h" |
| 16 | #include "include/policy.h" |
| 17 | #include "include/policy_ns.h" |
| 18 | #include "include/secid.h" |
| 19 | |
| 20 | const char *const audit_mode_names[] = { |
| 21 | "normal", |
| 22 | "quiet_denied", |
| 23 | "quiet", |
| 24 | "noquiet", |
| 25 | "all" |
| 26 | }; |
| 27 | |
| 28 | static const char *const aa_audit_type[] = { |
| 29 | "AUDIT", |
| 30 | "ALLOWED", |
| 31 | "DENIED", |
| 32 | "HINT", |
| 33 | "STATUS", |
| 34 | "ERROR", |
| 35 | "KILLED", |
| 36 | "AUTO" |
| 37 | }; |
| 38 | |
| 39 | static const char *const aa_class_names[] = { |
| 40 | "none", |
| 41 | "unknown", |
| 42 | "file", |
| 43 | "cap", |
| 44 | "net", |
| 45 | "rlimits", |
| 46 | "domain", |
| 47 | "mount", |
| 48 | "unknown", |
| 49 | "ptrace", |
| 50 | "signal", |
| 51 | "xmatch", |
| 52 | "unknown", |
| 53 | "unknown", |
| 54 | "net", |
| 55 | "unknown", |
| 56 | "label", |
| 57 | "posix_mqueue", |
| 58 | "io_uring", |
| 59 | "module", |
| 60 | "lsm", |
| 61 | "namespace", |
| 62 | "io_uring", |
| 63 | "unknown", |
| 64 | "unknown", |
| 65 | "unknown", |
| 66 | "unknown", |
| 67 | "unknown", |
| 68 | "unknown", |
| 69 | "unknown", |
| 70 | "unknown", |
| 71 | "X", |
| 72 | "dbus", |
| 73 | }; |
| 74 | |
| 75 | |
| 76 | /* |
| 77 | * Currently AppArmor auditing is fed straight into the audit framework. |
| 78 | * |
| 79 | * TODO: |
| 80 | * netlink interface for complain mode |
| 81 | * user auditing, - send user auditing to netlink interface |
| 82 | * system control of whether user audit messages go to system log |
| 83 | */ |
| 84 | |
| 85 | /** |
| 86 | * audit_pre() - core AppArmor function. |
| 87 | * @ab: audit buffer to fill (NOT NULL) |
| 88 | * @va: audit structure containing data to audit (NOT NULL) |
| 89 | * |
| 90 | * Record common AppArmor audit data from @va |
| 91 | */ |
| 92 | static void audit_pre(struct audit_buffer *ab, void *va) |
| 93 | { |
| 94 | struct apparmor_audit_data *ad = aad_of_va(va); |
| 95 | |
| 96 | if (aa_g_audit_header) { |
| 97 | audit_log_format(ab, "apparmor=\"%s\"", |
| 98 | aa_audit_type[ad->type]); |
| 99 | } |
| 100 | |
| 101 | if (ad->op) |
| 102 | audit_log_format(ab, " operation=\"%s\"", ad->op); |
| 103 | |
| 104 | if (ad->class) |
| 105 | audit_log_format(ab, " class=\"%s\"", |
| 106 | ad->class <= AA_CLASS_LAST ? |
| 107 | aa_class_names[ad->class] : |
| 108 | "unknown"); |
| 109 | |
| 110 | if (ad->info) { |
| 111 | audit_log_format(ab, " info=\"%s\"", ad->info); |
| 112 | if (ad->error) |
| 113 | audit_log_format(ab, " error=%d", ad->error); |
| 114 | } |
| 115 | |
| 116 | if (ad->subj_label) { |
| 117 | struct aa_label *label = ad->subj_label; |
| 118 | |
| 119 | if (label_isprofile(label)) { |
| 120 | struct aa_profile *profile = labels_profile(label); |
| 121 | |
| 122 | if (profile->ns != root_ns) { |
| 123 | audit_log_format(ab, " namespace="); |
| 124 | audit_log_untrustedstring(ab, |
| 125 | profile->ns->base.hname); |
| 126 | } |
| 127 | audit_log_format(ab, " profile="); |
| 128 | audit_log_untrustedstring(ab, profile->base.hname); |
| 129 | } else { |
| 130 | audit_log_format(ab, " label="); |
| 131 | aa_label_xaudit(ab, root_ns, label, FLAG_VIEW_SUBNS, |
| 132 | GFP_ATOMIC); |
| 133 | } |
| 134 | } |
| 135 | |
| 136 | if (ad->name) { |
| 137 | audit_log_format(ab, " name="); |
| 138 | audit_log_untrustedstring(ab, ad->name); |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | /** |
| 143 | * aa_audit_msg - Log a message to the audit subsystem |
| 144 | * @type: audit type for the message |
| 145 | * @ad: audit event structure (NOT NULL) |
| 146 | * @cb: optional callback fn for type specific fields (MAYBE NULL) |
| 147 | */ |
| 148 | void aa_audit_msg(int type, struct apparmor_audit_data *ad, |
| 149 | void (*cb) (struct audit_buffer *, void *)) |
| 150 | { |
| 151 | ad->type = type; |
| 152 | common_lsm_audit(&ad->common, audit_pre, cb); |
| 153 | } |
| 154 | |
| 155 | /** |
| 156 | * aa_audit - Log a profile based audit event to the audit subsystem |
| 157 | * @type: audit type for the message |
| 158 | * @profile: profile to check against (NOT NULL) |
| 159 | * @ad: audit event (NOT NULL) |
| 160 | * @cb: optional callback fn for type specific fields (MAYBE NULL) |
| 161 | * |
| 162 | * Handle default message switching based off of audit mode flags |
| 163 | * |
| 164 | * Returns: error on failure |
| 165 | */ |
| 166 | int aa_audit(int type, struct aa_profile *profile, |
| 167 | struct apparmor_audit_data *ad, |
| 168 | void (*cb) (struct audit_buffer *, void *)) |
| 169 | { |
| 170 | AA_BUG(!profile); |
| 171 | |
| 172 | if (type == AUDIT_APPARMOR_AUTO) { |
| 173 | if (likely(!ad->error)) { |
| 174 | if (AUDIT_MODE(profile) != AUDIT_ALL) |
| 175 | return 0; |
| 176 | type = AUDIT_APPARMOR_AUDIT; |
| 177 | } else if (COMPLAIN_MODE(profile)) |
| 178 | type = AUDIT_APPARMOR_ALLOWED; |
| 179 | else |
| 180 | type = AUDIT_APPARMOR_DENIED; |
| 181 | } |
| 182 | if (AUDIT_MODE(profile) == AUDIT_QUIET || |
| 183 | (type == AUDIT_APPARMOR_DENIED && |
| 184 | AUDIT_MODE(profile) == AUDIT_QUIET_DENIED)) |
| 185 | return ad->error; |
| 186 | |
| 187 | if (KILL_MODE(profile) && type == AUDIT_APPARMOR_DENIED) |
| 188 | type = AUDIT_APPARMOR_KILL; |
| 189 | |
| 190 | ad->subj_label = &profile->label; |
| 191 | |
| 192 | aa_audit_msg(type, ad, cb); |
| 193 | |
| 194 | if (ad->type == AUDIT_APPARMOR_KILL) |
| 195 | (void)send_sig_info(SIGKILL, NULL, |
| 196 | ad->common.type == LSM_AUDIT_DATA_TASK && |
| 197 | ad->common.u.tsk ? ad->common.u.tsk : current); |
| 198 | |
| 199 | if (ad->type == AUDIT_APPARMOR_ALLOWED) |
| 200 | return complain_error(ad->error); |
| 201 | |
| 202 | return ad->error; |
| 203 | } |
| 204 | |
| 205 | struct aa_audit_rule { |
| 206 | struct aa_label *label; |
| 207 | }; |
| 208 | |
| 209 | void aa_audit_rule_free(void *vrule) |
| 210 | { |
| 211 | struct aa_audit_rule *rule = vrule; |
| 212 | |
| 213 | if (rule) { |
| 214 | if (!IS_ERR(rule->label)) |
| 215 | aa_put_label(rule->label); |
| 216 | kfree(rule); |
| 217 | } |
| 218 | } |
| 219 | |
| 220 | int aa_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule, gfp_t gfp) |
| 221 | { |
| 222 | struct aa_audit_rule *rule; |
| 223 | |
| 224 | switch (field) { |
| 225 | case AUDIT_SUBJ_ROLE: |
| 226 | if (op != Audit_equal && op != Audit_not_equal) |
| 227 | return -EINVAL; |
| 228 | break; |
| 229 | default: |
| 230 | return -EINVAL; |
| 231 | } |
| 232 | |
| 233 | rule = kzalloc(sizeof(struct aa_audit_rule), gfp); |
| 234 | |
| 235 | if (!rule) |
| 236 | return -ENOMEM; |
| 237 | |
| 238 | /* Currently rules are treated as coming from the root ns */ |
| 239 | rule->label = aa_label_parse(&root_ns->unconfined->label, rulestr, |
| 240 | gfp, true, false); |
| 241 | if (IS_ERR(rule->label)) { |
| 242 | int err = PTR_ERR(rule->label); |
| 243 | aa_audit_rule_free(rule); |
| 244 | return err; |
| 245 | } |
| 246 | |
| 247 | *vrule = rule; |
| 248 | return 0; |
| 249 | } |
| 250 | |
| 251 | int aa_audit_rule_known(struct audit_krule *rule) |
| 252 | { |
| 253 | int i; |
| 254 | |
| 255 | for (i = 0; i < rule->field_count; i++) { |
| 256 | struct audit_field *f = &rule->fields[i]; |
| 257 | |
| 258 | switch (f->type) { |
| 259 | case AUDIT_SUBJ_ROLE: |
| 260 | return 1; |
| 261 | } |
| 262 | } |
| 263 | |
| 264 | return 0; |
| 265 | } |
| 266 | |
| 267 | int aa_audit_rule_match(struct lsm_prop *prop, u32 field, u32 op, void *vrule) |
| 268 | { |
| 269 | struct aa_audit_rule *rule = vrule; |
| 270 | struct aa_label *label; |
| 271 | int found = 0; |
| 272 | |
| 273 | label = prop->apparmor.label; |
| 274 | |
| 275 | if (!label) |
| 276 | return -ENOENT; |
| 277 | |
| 278 | if (aa_label_is_subset(label, rule->label)) |
| 279 | found = 1; |
| 280 | |
| 281 | switch (field) { |
| 282 | case AUDIT_SUBJ_ROLE: |
| 283 | switch (op) { |
| 284 | case Audit_equal: |
| 285 | return found; |
| 286 | case Audit_not_equal: |
| 287 | return !found; |
| 288 | } |
| 289 | } |
| 290 | return 0; |
| 291 | } |