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