1 # bend-osdev-apparmor: the access decision. 2 # 3 # The rules of one kind that match a request each contribute a grant 4 # (perm.bend). Allow grants accumulate into the allowed set, deny grants 5 # into the denied set (rules accumulate as a union: apparmor.d(5), "order 6 # ... does not matter"); what is granted is allowed minus denied, so a deny 7 # rule always wins. A request is granted when every permission it asks for 8 # is. Then the profile mode decides (apparmor.d(5), "Profile Mode"; 9 # apparmor(7), "Complain mode"): 10 # enforce, prompt: granted -> allowed, else denied; 11 # kill: granted -> allowed, else denied and the task killed; 12 # complain: granted -> allowed, else allowed and logged, except that a 13 # request an explicit deny rule meets is denied (AaOptions.complain_deny); 14 # default_allow: allowed unless a deny rule meets the request; 15 # unconfined: allowed. 16 # Logging: an allowed request is audited when the profile has the audit 17 # flag or an audit rule grants a requested permission; a denial is logged 18 # unless every refused permission is quieted by a deny rule without audit. 19 import Base 20 import ./perm.bend as P 21 import ./options.bend as O 22 import ./profile.bend as PR 23 import ./rule_file.bend as F 24 import ./rule_capability.bend as RC 25 import ./rule_network.bend as RN 26 import ./rule_signal.bend as RS 27 import ./rule_ptrace.bend as RP 28 import ./rule_mount.bend as RM 29 import ./rule_dbus.bend as RD 30 import ./rule_change_profile.bend as RX 31 32 type AaDecision is Data: 33 # allowed, not logged 34 AaAllow{} 35 # allowed, logged as AUDIT 36 AaAudit{} 37 # not granted, allowed in complain mode, logged as ALLOWED 38 AaComplain{} 39 # denied (EACCES or EPERM), logged as DENIED unless quieted 40 AaDeny{logged: Bool} 41 # denied, and the task is killed (kill mode) 42 AaKill{} 43 44 def AaDecision.allowed(d: AaDecision) -> Bool: 45 match d: 46 case AaAllow{}: 47 True{} 48 case AaAudit{}: 49 True{} 50 case AaComplain{}: 51 True{} 52 case AaDeny{l}: 53 False{} 54 case AaKill{}: 55 False{} 56 57 def AaDecision.show(d: AaDecision) -> String: 58 match d: 59 case AaAllow{}: 60 "allow" 61 case AaAudit{}: 62 "audit" 63 case AaComplain{}: 64 "complain" 65 case AaDeny{l}: 66 Bool.pick(String, l, "deny", "deny-quiet") 67 case AaKill{}: 68 "kill" 69 70 # What the grants add up to 71 72 type AaAcc is Data: 73 AaAcc{allow: P.AaPerms, deny: P.AaPerms, audit: P.AaPerms, quiet: P.AaPerms} 74 75 def AaAcc.empty() -> AaAcc: 76 AaAcc{P.AaPerms.none(), P.AaPerms.none(), P.AaPerms.none(), P.AaPerms.none()} 77 78 # two accumulations together: each set the union 79 def AaAcc.join(a: AaAcc, b: AaAcc) -> AaAcc: 80 match a b: 81 case AaAcc{al, de, au, qu} AaAcc{al2, de2, au2, qu2}: 82 AaAcc{P.AaPerms.union(al, al2), P.AaPerms.union(de, de2), P.AaPerms.union(au, au2), P.AaPerms.union(qu, qu2)} 83 84 # what one grant contributes: allowed or denied; audited, or (for a denial 85 # without audit) quieted 86 def AaAcc.single.go(audit: Bool, deny: Bool, +p: P.AaPerms) -> AaAcc: 87 match audit: 88 case True{}: 89 match deny: 90 case True{}: 91 AaAcc{P.AaPerms.none(), p, p, P.AaPerms.none()} 92 case False{}: 93 AaAcc{p, P.AaPerms.none(), p, P.AaPerms.none()} 94 case False{}: 95 match deny: 96 case True{}: 97 AaAcc{P.AaPerms.none(), p, P.AaPerms.none(), p} 98 case False{}: 99 AaAcc{p, P.AaPerms.none(), P.AaPerms.none(), P.AaPerms.none()} 100 101 def AaAcc.single(g: P.AaGrant) -> AaAcc: 102 match g: 103 case P.AaGrant{audit, deny, p}: 104 AaAcc.single.go(audit, deny, p) 105 106 def AaAcc.add(a: AaAcc, g: P.AaGrant) -> AaAcc: 107 AaAcc.join(a, AaAcc.single(g)) 108 109 def AaAcc.from(gs: List<&2, P.AaGrant>, a: AaAcc) -> AaAcc: 110 match gs: 111 case Nil{}: 112 a 113 case Con{g, t}: 114 AaAcc.from(t, AaAcc.add(a, g)) 115 116 def AaAcc.of(gs: List<&2, P.AaGrant>) -> AaAcc: 117 AaAcc.from(gs, AaAcc.empty()) 118 119 def AaAcc.allow(a: AaAcc) -> P.AaPerms: 120 match a: 121 case AaAcc{al, de, au, qu}: 122 al 123 124 def AaAcc.deny(a: AaAcc) -> P.AaPerms: 125 match a: 126 case AaAcc{al, de, au, qu}: 127 de 128 129 def AaAcc.audit(a: AaAcc) -> P.AaPerms: 130 match a: 131 case AaAcc{al, de, au, qu}: 132 au 133 134 def AaAcc.quiet(a: AaAcc) -> P.AaPerms: 135 match a: 136 case AaAcc{al, de, au, qu}: 137 qu 138 139 # what the grants allow and do not deny 140 def AaAcc.granted(a: AaAcc) -> P.AaPerms: 141 match a: 142 case AaAcc{al, de, au, qu}: 143 P.AaPerms.minus(al, de) 144 145 def AaEval.granted(gs: List<&2, P.AaGrant>) -> P.AaPerms: 146 AaAcc.granted(AaAcc.of(gs)) 147 148 # what the grants deny 149 def AaEval.denied(gs: List<&2, P.AaGrant>) -> P.AaPerms: 150 AaAcc.deny(AaAcc.of(gs)) 151 152 # The decision 153 154 def AaEval.choose(c: Bool, a: AaDecision, b: AaDecision) -> AaDecision: 155 match c: 156 case True{}: 157 a 158 case False{}: 159 b 160 161 # cov: the grants cover the request; hit: a deny rule meets it; cd: 162 # AaOptions.complain_deny; ok: the allowed outcome; no: the denied one 163 def AaEval.decide.mode(m: PR.AaMode, cov: Bool, hit: Bool, cd: Bool, ok: AaDecision, no: AaDecision) -> AaDecision: 164 match m: 165 case PR.AaModeEnforce{}: 166 AaEval.choose(cov, ok, no) 167 case PR.AaModePrompt{}: 168 AaEval.choose(cov, ok, no) 169 case PR.AaModeKill{}: 170 AaEval.choose(cov, ok, AaKill{}) 171 case PR.AaModeComplain{}: 172 AaEval.choose(cov, ok, AaEval.choose(Bool.and(cd, hit), no, AaComplain{})) 173 case PR.AaModeDefaultAllow{}: 174 AaEval.choose(hit, no, ok) 175 case PR.AaModeUnconfined{}: 176 AaAllow{} 177 178 def AaEval.decide.acc(opts: O.AaOptions, flags: PR.AaFlags, +req: P.AaPerms, a: AaAcc) -> AaDecision: 179 match flags: 180 case PR.AaFlags{m, +fa}: 181 match a: 182 case AaAcc{al, +de, +au, qu}: 183 +granted = P.AaPerms.minus(al, de) 184 AaEval.decide.mode(m, P.AaPerms.covers(granted, req), P.AaPerms.meets(req, de), O.AaOptions.complain_deny(opts), AaEval.choose(Bool.or(fa, P.AaPerms.meets(req, au)), AaAudit{}, AaAllow{}), AaDeny{Bool.or(fa, Bool.not(P.AaPerms.covers(P.AaPerms.minus(qu, au), P.AaPerms.minus(req, granted))))}) 185 186 # the decision on a request for the permissions req, given the grants of 187 # the rules that match it, under a profile's flags 188 def AaEval.decide(opts: O.AaOptions, flags: PR.AaFlags, +req: P.AaPerms, gs: List<&2, P.AaGrant>) -> AaDecision: 189 AaEval.decide.acc(opts, flags, req, AaAcc.of(gs)) 190 191 # whether a deny rule decides under these options and mode: every mode but 192 # unconfined, and complain only with complain_deny 193 def AaEval.deny_applies(opts: O.AaOptions, m: PR.AaMode) -> Bool: 194 match m: 195 case PR.AaModeUnconfined{}: 196 False{} 197 case PR.AaModeComplain{}: 198 O.AaOptions.complain_deny(opts) 199 case PR.AaModeEnforce{}: 200 True{} 201 case PR.AaModeKill{}: 202 True{} 203 case PR.AaModePrompt{}: 204 True{} 205 case PR.AaModeDefaultAllow{}: 206 True{} 207 208 # Each rule kind, on a profile 209 210 def AaEval.file(opts: O.AaOptions, +p: PR.AaProfile, +q: F.AaFileQuery) -> AaDecision: 211 match q: 212 case F.AaFileQuery{path, owner, req}: 213 AaEval.decide(opts, PR.AaProfile.flags(p), req, F.AaFileRule.grants(PR.AaRules.files(PR.AaProfile.rules(p)), F.AaFileQuery{path, owner, req})) 214 215 def AaEval.capability(opts: O.AaOptions, +p: PR.AaProfile, +q: RC.AaCapQuery) -> AaDecision: 216 AaEval.decide(opts, PR.AaProfile.flags(p), RC.AaCapQuery.req(), RC.AaCapRule.grants(PR.AaRules.caps(PR.AaProfile.rules(p)), q)) 217 218 def AaEval.network(opts: O.AaOptions, +p: PR.AaProfile, +q: RN.AaNetQuery) -> AaDecision: 219 AaEval.decide(opts, PR.AaProfile.flags(p), RN.AaNetQuery.req(), RN.AaNetRule.grants(PR.AaRules.nets(PR.AaProfile.rules(p)), q)) 220 221 def AaEval.signal(opts: O.AaOptions, +p: PR.AaProfile, +q: RS.AaSignalQuery) -> AaDecision: 222 AaEval.decide(opts, PR.AaProfile.flags(p), RS.AaSignalQuery.req(q), RS.AaSignalRule.grants(PR.AaRules.signals(PR.AaProfile.rules(p)), q)) 223 224 def AaEval.ptrace(opts: O.AaOptions, +p: PR.AaProfile, +q: RP.AaPtraceQuery) -> AaDecision: 225 AaEval.decide(opts, PR.AaProfile.flags(p), RP.AaPtraceQuery.req(q), RP.AaPtraceRule.grants(PR.AaRules.ptraces(PR.AaProfile.rules(p)), q)) 226 227 def AaEval.mount(opts: O.AaOptions, +p: PR.AaProfile, +q: RM.AaMountQuery) -> AaDecision: 228 AaEval.decide(opts, PR.AaProfile.flags(p), RM.AaMountQuery.req(), RM.AaMountRule.grants(PR.AaRules.mounts(PR.AaProfile.rules(p)), q)) 229 230 def AaEval.dbus(opts: O.AaOptions, +p: PR.AaProfile, +q: RD.AaDbusQuery) -> AaDecision: 231 AaEval.decide(opts, PR.AaProfile.flags(p), RD.AaDbusQuery.req(q), RD.AaDbusRule.grants(PR.AaRules.dbus(PR.AaProfile.rules(p)), q)) 232 233 def AaEval.change_profile(opts: O.AaOptions, +p: PR.AaProfile, +q: RX.AaChangeQuery) -> AaDecision: 234 AaEval.decide(opts, PR.AaProfile.flags(p), RX.AaChangeQuery.req(), RX.AaChangeRule.grants(PR.AaRules.changes(PR.AaProfile.rules(p)), q))