]>
Commit | Line | Data |
---|---|---|
62e4ed3d A |
1 | #!/usr/bin/python |
2 | # | |
3 | # gkrecord - record Gatekeeper rejection activity | |
4 | # | |
5 | # gkrecord filename | |
6 | # | |
7 | import sys | |
8 | import os | |
9 | import signal | |
10 | import errno | |
11 | import subprocess | |
12 | import tempfile | |
13 | import plistlib | |
14 | ||
15 | ||
16 | # | |
17 | # Usage and fail | |
18 | # | |
19 | def usage(): | |
20 | print >>sys.stderr, "Usage: %s outputfile" % sys.argv[0] | |
21 | sys.exit(2) | |
22 | ||
23 | def fail(whatever): | |
24 | print >>sys.stderr, "%s: %s" % (sys.argv[0], whatever) | |
25 | sys.exit(1) | |
26 | ||
27 | ||
28 | # | |
29 | # Argument processing | |
30 | # | |
31 | if len(sys.argv) != 2: | |
32 | usage() | |
33 | outputfile = sys.argv[1] | |
34 | ||
35 | ||
36 | # | |
37 | # If the output file already exists, bail | |
38 | # | |
39 | if os.path.exists(outputfile): | |
40 | fail("already exists: %s" % outputfile) | |
41 | ||
42 | ||
43 | # | |
44 | # Places and things | |
45 | # | |
46 | collect = "/tmp/gke/" | |
47 | ||
48 | ||
49 | # must be root | |
50 | if os.getuid() != 0: | |
51 | fail("Must have root privileges") | |
52 | ||
53 | ||
54 | # | |
55 | # Make sure Gatekeeper is disabled | |
56 | # | |
57 | subprocess.check_call(["/usr/sbin/spctl", "--master-disable"]) | |
58 | ||
59 | ||
60 | # | |
61 | # make sure we have a fresh syspolicyd and get its pid | |
62 | # | |
63 | subprocess.check_call(["/usr/sbin/spctl", "--assess", "--ignore-cache", "/bin/ls"]) | |
64 | try: | |
65 | psax = subprocess.check_output("ps ax|grep syspolicyd|grep -v grep", shell=True).split("\n") | |
66 | if len(psax) != 2: # [ found_syspolicyd, '' ] | |
67 | fail("Cannot find syspolicyd") | |
68 | spd_pid = int(psax[0].split()[0]) | |
69 | except subprocess.CalledProcessError: | |
70 | fail("Cannot find syspolicyd") | |
71 | ||
72 | ||
73 | # | |
74 | # run collector dtrace script until dtrace dies. | |
75 | # recorder_mode arguments are (path, type, label, cdhash, flags) | |
76 | # | |
77 | DSCRIPT = ''' | |
78 | syspolicy$1:::recorder_mode { printf("RECORD;%d;%d", arg1, arg4); } | |
79 | ||
80 | self unsigned char *cdhash; | |
81 | ||
82 | syspolicy$1:::recorder_mode | |
83 | { | |
84 | self->cdhash = copyin(arg3, 20); | |
85 | printf(";%02.2x%02.2x%02.2x%02.2x%02.2x%02.2x%02.2x%02.2x%02.2x%02.2x%02.2x%02.2x%02.2x%02.2x%02.2x%02.2x%02.2x%02.2x%02.2x%02.2x", | |
86 | self->cdhash[0], self->cdhash[1], self->cdhash[2], self->cdhash[3], self->cdhash[4], | |
87 | self->cdhash[5], self->cdhash[6], self->cdhash[7], self->cdhash[8], self->cdhash[9], | |
88 | self->cdhash[10], self->cdhash[11], self->cdhash[12], self->cdhash[13], self->cdhash[14], | |
89 | self->cdhash[15], self->cdhash[16], self->cdhash[17], self->cdhash[18], self->cdhash[19]); | |
90 | printf(";%s\\n", copyinstr(arg0)); | |
91 | } | |
92 | ||
93 | syspolicy$1:::recorder_mode_adhoc_path | |
94 | { | |
95 | printf("SIGNATURE;%d;%s;%s\\n", arg1, copyinstr(arg2), copyinstr(arg0)); | |
96 | } | |
97 | ||
98 | syspolicy$1:::assess-outcome-unsigned | |
99 | { | |
100 | printf("UNSIGNED;%d;%s\\n", arg1, copyinstr(arg0)); | |
101 | } | |
102 | ||
103 | syspolicy$1:::assess-outcome-broken | |
104 | { | |
105 | printf("BROKEN;%d;%d;%s\\n", arg1, arg2, copyinstr(arg0)); | |
106 | } | |
107 | ''' | |
108 | ||
109 | def sigint(sig, ctx): | |
110 | os.kill(spd_pid, signal.SIGINT) | |
111 | signal.signal(signal.SIGINT, sigint) | |
112 | ||
113 | (authfd, authfile) = tempfile.mkstemp() | |
114 | dtrace = subprocess.Popen(["dtrace", "-qs", "/dev/stdin", str(spd_pid)], stdin=subprocess.PIPE, stdout=authfd, stderr=subprocess.PIPE) | |
115 | print "Exercise the programs to be whitelisted now. Interrupt this script (^C) when you are done." | |
116 | (stdout, stderr) = dtrace.communicate(input=DSCRIPT) | |
117 | signal.signal(signal.SIGINT, signal.SIG_DFL) | |
118 | if stderr: | |
119 | fail("dtrace failed: %s" % stderr) | |
120 | os.lseek(authfd, os.SEEK_SET, 0) # rewind | |
121 | ||
122 | ||
123 | # | |
124 | # Collect all the data into dicts | |
125 | # | |
126 | auth = { } | |
127 | sigs = { } | |
128 | unsigned = { } | |
129 | badsigned = { } | |
130 | errors = { } | |
131 | ||
132 | file = os.fdopen(authfd, "r") | |
133 | for line in file: | |
134 | (cmd, s, args) = line.strip().partition(";") | |
135 | if s != ";": | |
136 | continue # spurious | |
137 | # print cmd, "--->", args | |
138 | if cmd == "RECORD": | |
139 | (type, status, cdhash, path) = args.split(";", 3) | |
140 | auth[path] = dict( | |
141 | path=path, | |
142 | type=type, | |
143 | status=status, | |
144 | cdhash=cdhash | |
145 | ) | |
146 | elif cmd == "SIGNATURE": | |
147 | (type, sigpath, path) = args.split(";", 2) | |
148 | with open(sigpath, "r") as sigfile: | |
149 | sigdata = sigfile.read() | |
150 | sigs[path] = dict( | |
151 | path=path, | |
152 | type=type, | |
153 | signature=plistlib.Data(sigdata) | |
154 | ) | |
155 | elif cmd == "UNSIGNED": | |
156 | (type, path) = args.split(";", 1) | |
157 | unsigned[path] = dict( | |
158 | path=path, | |
159 | type=type | |
160 | ) | |
161 | elif cmd == "BROKEN": | |
162 | (type, exception, path) = args.split(";", 2) | |
163 | badsigned[path] = dict( | |
164 | path=path, | |
165 | type=type, | |
166 | exception=exception | |
167 | ) | |
168 | ||
169 | # unsigned code that had a good detached signature recorded is okay | |
170 | for rec in sigs: | |
171 | if rec in unsigned: | |
172 | del unsigned[rec] | |
173 | ||
174 | ||
175 | # | |
176 | # Pack them up as a single output (plist) file | |
177 | # | |
178 | gkedict = dict( | |
179 | authority = auth, | |
180 | signatures = sigs | |
181 | ) | |
182 | plistlib.writePlist(gkedict, outputfile) | |
183 | ||
184 | ||
185 | # | |
186 | # Report on any problems found | |
187 | # | |
188 | for rec in unsigned.values(): | |
189 | print >>sys.stderr, "PROBLEM: unsigned type %d object not whitelisted: %s" % (rec["type"], rec["path"]) | |
190 | for rec in badsigned.values(): | |
191 | print >>sys.stderr, "PROBLEM: broken code signature; object not whitelisted: %s" % rec["path"] | |
192 | ||
193 | ||
194 | # | |
195 | # Done | |
196 | # | |
197 | print "Recorded %d authorization(s), %d signature(s) in %s" % (len(auth), len(sigs), outputfile) | |
198 | sys.exit(0) |