eval.bend

bend-osdev-apparmor@0.1.0.0 · 234 lines · 31 definitions

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.
19import Base
20import bend-osdev-apparmor@0.1.0.0/perm.bend as P
21import bend-osdev-apparmor@0.1.0.0/options.bend as O
22import bend-osdev-apparmor@0.1.0.0/profile.bend as PR
23import bend-osdev-apparmor@0.1.0.0/rule_file.bend as F
24import bend-osdev-apparmor@0.1.0.0/rule_capability.bend as RC
25import bend-osdev-apparmor@0.1.0.0/rule_network.bend as RN
26import bend-osdev-apparmor@0.1.0.0/rule_signal.bend as RS
27import bend-osdev-apparmor@0.1.0.0/rule_ptrace.bend as RP
28import bend-osdev-apparmor@0.1.0.0/rule_mount.bend as RM
29import bend-osdev-apparmor@0.1.0.0/rule_dbus.bend as RD
30import bend-osdev-apparmor@0.1.0.0/rule_change_profile.bend as RX
31
32type 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
44def 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
57def 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
72type AaAcc is Data:
73  AaAcc{allow: P.AaPerms, deny: P.AaPerms, audit: P.AaPerms, quiet: P.AaPerms}
74
75def 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
79def 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
86def 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
101def AaAcc.single(g: P.AaGrant) -> AaAcc:
102  match g:
103    case P.AaGrant{audit, deny, p}:
104      AaAcc.single.go(audit, deny, p)
105
106def AaAcc.add(a: AaAcc, g: P.AaGrant) -> AaAcc:
107  AaAcc.join(a, AaAcc.single(g))
108
109def 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
116def AaAcc.of(gs: List<&2, P.AaGrant>) -> AaAcc:
117  AaAcc.from(gs, AaAcc.empty())
118
119def AaAcc.allow(a: AaAcc) -> P.AaPerms:
120  match a:
121    case AaAcc{al, de, au, qu}:
122      al
123
124def AaAcc.deny(a: AaAcc) -> P.AaPerms:
125  match a:
126    case AaAcc{al, de, au, qu}:
127      de
128
129def AaAcc.audit(a: AaAcc) -> P.AaPerms:
130  match a:
131    case AaAcc{al, de, au, qu}:
132      au
133
134def 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
140def AaAcc.granted(a: AaAcc) -> P.AaPerms:
141  match a:
142    case AaAcc{al, de, au, qu}:
143      P.AaPerms.minus(al, de)
144
145def AaEval.granted(gs: List<&2, P.AaGrant>) -> P.AaPerms:
146  AaAcc.granted(AaAcc.of(gs))
147
148# what the grants deny
149def AaEval.denied(gs: List<&2, P.AaGrant>) -> P.AaPerms:
150  AaAcc.deny(AaAcc.of(gs))
151
152# The decision
153
154def 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
163def 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
178def 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
188def 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
193def 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
210def 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
215def 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
218def 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
221def 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
224def 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
227def 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
230def 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
233def 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))