1 # bend-osdev-apparmor: AppArmor policy, as a pure model: loading profiles 2 # from text (apparmor.d(5)) and answering what they allow. 3 # 4 # Aa.load reads a policy from a file map (each file a name and its text, as 5 # Octets) and the name of the file to start from. The queries name a 6 # profile by its full name (a, a//child, a//^hat is a//hat) and answer an 7 # AaDecision (eval.bend), or None when the policy has no such profile; 8 # Aa.exec answers where an exec goes (transitions.bend). 9 # 10 # The files below this one work on byte lists (List<&2, U32>, what 11 # Octets.to_list gives), which is what their laws are stated over. 12 import Base 13 import bend-osdev-bytes@0.1.0.0/bytes.bend as B 14 import ./error.bend as E 15 import ./perm.bend as P 16 import ./options.bend as O 17 import ./profile.bend as PR 18 import ./parse.bend as S 19 import ./eval.bend as V 20 import ./transitions.bend as TR 21 import ./rule_file.bend as F 22 import ./rule_capability.bend as RC 23 import ./rule_network.bend as RN 24 import ./rule_signal.bend as RS 25 import ./rule_ptrace.bend as RP 26 import ./rule_mount.bend as RM 27 import ./rule_dbus.bend as RD 28 import ./rule_change_profile.bend as RX 29 30 # a file of the policy: its name as includes name it (abstractions/base 31 # for ), and its text 32 type AaSource is Data: 33 AaSource{name: B.Octets, text: B.Octets} 34 35 def Aa.files(xs: List<&2, AaSource>) -> List<&2, S.AaSrcFile>: 36 match xs: 37 case Nil{}: 38 Nil{} 39 case Con{x, t}: 40 match x: 41 case AaSource{n, s}: 42 Con{S.AaSrcFile{B.Octets.to_list(n), B.Octets.to_list(s)}, Aa.files(t)} 43 44 # the policy the file main defines, with its includes taken from sources 45 def Aa.load(opts: O.AaOptions, sources: List<&2, AaSource>, main: B.Octets) -> Result<&2, &2, E.AaError, PR.AaPolicy>: 46 S.AaParse.load(opts, Aa.files(sources), B.Octets.to_list(main)) 47 48 def Aa.load_default(sources: List<&2, AaSource>, main: B.Octets) -> Result<&2, &2, E.AaError, PR.AaPolicy>: 49 Aa.load(O.AaOptions.default(), sources, main) 50 51 def Aa.profile(pol: PR.AaPolicy, name: B.Octets) -> Maybe<&2, PR.AaProfile>: 52 PR.AaPolicy.lookup(pol, B.Octets.to_list(name)) 53 54 def Aa.on.file(m: Maybe<&2, PR.AaProfile>, opts: O.AaOptions, q: F.AaFileQuery) -> Maybe<&2, V.AaDecision>: 55 match m: 56 case None{}: 57 None{} 58 case Some{p}: 59 Some{V.AaEval.file(opts, p, q)} 60 61 # may the profile access path with the permissions req (owner: the task owns the file)? 62 def Aa.file(opts: O.AaOptions, pol: PR.AaPolicy, profile: B.Octets, path: B.Octets, owner: Bool, req: P.AaPerms) -> Maybe<&2, V.AaDecision>: 63 Aa.on.file(Aa.profile(pol, profile), opts, F.AaFileQuery{B.Octets.to_list(path), owner, req}) 64 65 def Aa.on.capability(m: Maybe<&2, PR.AaProfile>, opts: O.AaOptions, q: RC.AaCapQuery) -> Maybe<&2, V.AaDecision>: 66 match m: 67 case None{}: 68 None{} 69 case Some{p}: 70 Some{V.AaEval.capability(opts, p, q)} 71 72 # may the profile use capability number cap? 73 def Aa.capability(opts: O.AaOptions, pol: PR.AaPolicy, profile: B.Octets, cap: U32) -> Maybe<&2, V.AaDecision>: 74 Aa.on.capability(Aa.profile(pol, profile), opts, RC.AaCapQuery{cap}) 75 76 def Aa.on.network(m: Maybe<&2, PR.AaProfile>, opts: O.AaOptions, q: RN.AaNetQuery) -> Maybe<&2, V.AaDecision>: 77 match m: 78 case None{}: 79 None{} 80 case Some{p}: 81 Some{V.AaEval.network(opts, p, q)} 82 83 # may the profile use a socket of this domain, type and protocol (names: inet, stream, tcp)? 84 def Aa.network(opts: O.AaOptions, pol: PR.AaPolicy, profile: B.Octets, domain: B.Octets, stype: B.Octets, proto: B.Octets) -> Maybe<&2, V.AaDecision>: 85 Aa.on.network(Aa.profile(pol, profile), opts, RN.AaNetQuery{B.Octets.to_list(domain), B.Octets.to_list(stype), B.Octets.to_list(proto)}) 86 87 def Aa.on.signal(m: Maybe<&2, PR.AaProfile>, opts: O.AaOptions, q: RS.AaSignalQuery) -> Maybe<&2, V.AaDecision>: 88 match m: 89 case None{}: 90 None{} 91 case Some{p}: 92 Some{V.AaEval.signal(opts, p, q)} 93 94 # may the profile send (receive) the signal named sig to (from) the peer profile? 95 def Aa.signal(opts: O.AaOptions, pol: PR.AaPolicy, profile: B.Octets, req: P.AaPerms, sig: B.Octets, peer: B.Octets) -> Maybe<&2, V.AaDecision>: 96 Aa.on.signal(Aa.profile(pol, profile), opts, RS.AaSignalQuery{req, B.Octets.to_list(sig), B.Octets.to_list(peer)}) 97 98 def Aa.on.ptrace(m: Maybe<&2, PR.AaProfile>, opts: O.AaOptions, q: RP.AaPtraceQuery) -> Maybe<&2, V.AaDecision>: 99 match m: 100 case None{}: 101 None{} 102 case Some{p}: 103 Some{V.AaEval.ptrace(opts, p, q)} 104 105 def Aa.ptrace(opts: O.AaOptions, pol: PR.AaPolicy, profile: B.Octets, req: P.AaPerms, peer: B.Octets) -> Maybe<&2, V.AaDecision>: 106 Aa.on.ptrace(Aa.profile(pol, profile), opts, RP.AaPtraceQuery{req, B.Octets.to_list(peer)}) 107 108 def Aa.on.mount(m: Maybe<&2, PR.AaProfile>, opts: O.AaOptions, q: RM.AaMountQuery) -> Maybe<&2, V.AaDecision>: 109 match m: 110 case None{}: 111 None{} 112 case Some{p}: 113 Some{V.AaEval.mount(opts, p, q)} 114 115 def Aa.mount(opts: O.AaOptions, pol: PR.AaPolicy, profile: B.Octets, q: RM.AaMountQuery) -> Maybe<&2, V.AaDecision>: 116 Aa.on.mount(Aa.profile(pol, profile), opts, q) 117 118 def Aa.on.dbus(m: Maybe<&2, PR.AaProfile>, opts: O.AaOptions, q: RD.AaDbusQuery) -> Maybe<&2, V.AaDecision>: 119 match m: 120 case None{}: 121 None{} 122 case Some{p}: 123 Some{V.AaEval.dbus(opts, p, q)} 124 125 def Aa.dbus(opts: O.AaOptions, pol: PR.AaPolicy, profile: B.Octets, q: RD.AaDbusQuery) -> Maybe<&2, V.AaDecision>: 126 Aa.on.dbus(Aa.profile(pol, profile), opts, q) 127 128 def Aa.on.change(m: Maybe<&2, PR.AaProfile>, opts: O.AaOptions, q: RX.AaChangeQuery) -> Maybe<&2, V.AaDecision>: 129 match m: 130 case None{}: 131 None{} 132 case Some{p}: 133 Some{V.AaEval.change_profile(opts, p, q)} 134 135 def Aa.opt_list(m: Maybe<&2, B.Octets>) -> Maybe<&2, List<&2, U32>>: 136 match m: 137 case None{}: 138 None{} 139 case Some{o}: 140 Some{B.Octets.to_list(o)} 141 142 # may the profile change_profile to target (now: exec None; at the exec of a program: Some)? 143 def Aa.change_profile(opts: O.AaOptions, pol: PR.AaPolicy, profile: B.Octets, exec: Maybe<&2, B.Octets>, target: B.Octets) -> Maybe<&2, V.AaDecision>: 144 Aa.on.change(Aa.profile(pol, profile), opts, RX.AaChangeQuery{Aa.opt_list(exec), B.Octets.to_list(target)}) 145 146 # where an exec of path by a task confined by profile goes 147 def Aa.exec(opts: O.AaOptions, pol: PR.AaPolicy, profile: B.Octets, path: B.Octets, owner: Bool) -> TR.AaExecResult: 148 TR.AaTrans.exec(opts, pol, B.Octets.to_list(profile), owner, B.Octets.to_list(path)) 149 150 # where an exec of path by an unconfined task goes 151 def Aa.exec_unconfined(pol: PR.AaPolicy, path: B.Octets) -> TR.AaExecResult: 152 TR.AaTrans.exec_unconfined(pol, B.Octets.to_list(path))