mirror of
https://github.com/google/santa.git
synced 2026-01-15 09:17:59 -05:00
Compare commits
636 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d116f7b01e | ||
|
|
63ca34bc54 | ||
|
|
c894029c33 | ||
|
|
de2bdd6653 | ||
|
|
2d066ad671 | ||
|
|
24854d4ad7 | ||
|
|
99ee0af178 | ||
|
|
bf6f78df09 | ||
|
|
c05806916b | ||
|
|
e48ce0cfe3 | ||
|
|
eabca469b9 | ||
|
|
f6dc36e812 | ||
|
|
ac7cbdfd16 | ||
|
|
d1d008af0a | ||
|
|
5db56e01f5 | ||
|
|
726c49bec5 | ||
|
|
ae5db5dde7 | ||
|
|
2671807f0e | ||
|
|
70c8626016 | ||
|
|
436c472a49 | ||
|
|
ed5be6b062 | ||
|
|
a38f24728a | ||
|
|
4af026356f | ||
|
|
c6e1bb5618 | ||
|
|
e64d2e7ad4 | ||
|
|
3d393e9aa4 | ||
|
|
b8f3122ee9 | ||
|
|
8acfa6591e | ||
|
|
25b75b0e1b | ||
|
|
cb01b77f84 | ||
|
|
61582a0324 | ||
|
|
a17b5d51a4 | ||
|
|
447ea8674b | ||
|
|
c5eec850e1 | ||
|
|
1870631150 | ||
|
|
20ed1659c1 | ||
|
|
258de3efba | ||
|
|
394fd5fab9 | ||
|
|
53b7ef86ed | ||
|
|
423479771e | ||
|
|
933271826b | ||
|
|
880170ea7d | ||
|
|
e58ec37881 | ||
|
|
dece50dd10 | ||
|
|
9db9fc6009 | ||
|
|
f38c030805 | ||
|
|
d8060d3af9 | ||
|
|
34b4090b42 | ||
|
|
c6ca3d64b3 | ||
|
|
4913426631 | ||
|
|
455a1c76c3 | ||
|
|
e5a5f6f9fb | ||
|
|
7ef88d06a5 | ||
|
|
bc82d7988b | ||
|
|
545fa858e4 | ||
|
|
71c917649e | ||
|
|
3781556cf5 | ||
|
|
765d10a7c3 | ||
|
|
3583113381 | ||
|
|
46cd60e579 | ||
|
|
8198e59736 | ||
|
|
c5f0f5d177 | ||
|
|
ebc93954be | ||
|
|
cb4d2984b3 | ||
|
|
4c2018ef67 | ||
|
|
06d8295d0a | ||
|
|
ef8e9975e9 | ||
|
|
31509f4b9c | ||
|
|
497c1f393f | ||
|
|
8334a245c7 | ||
|
|
e8826a2941 | ||
|
|
ef040c1e7d | ||
|
|
dc692c8256 | ||
|
|
e9c7bfc087 | ||
|
|
22c72625c8 | ||
|
|
65a2212890 | ||
|
|
0a7c08cafc | ||
|
|
831a32160b | ||
|
|
b186419e54 | ||
|
|
1dc579c00f | ||
|
|
abdd6c319a | ||
|
|
5dd93fadfa | ||
|
|
e6fcbf59df | ||
|
|
9fd04ed301 | ||
|
|
e4b5f595ce | ||
|
|
212b02589b | ||
|
|
42c3631995 | ||
|
|
2695355dd2 | ||
|
|
db0cd861d6 | ||
|
|
57d6a962de | ||
|
|
91608d7366 | ||
|
|
7d4f1ffc45 | ||
|
|
ba539bb555 | ||
|
|
d9ecbf06c0 | ||
|
|
01df4623c7 | ||
|
|
c9cb91a22e | ||
|
|
1f9d60aecc | ||
|
|
52c5b5aade | ||
|
|
2d98173c51 | ||
|
|
5e3f13be70 | ||
|
|
90b894b88a | ||
|
|
6dc7387881 | ||
|
|
b14b017d72 | ||
|
|
d0ede18bf4 | ||
|
|
6d223aea03 | ||
|
|
f7986b0a05 | ||
|
|
629e70287c | ||
|
|
3c2a88144c | ||
|
|
3651f18566 | ||
|
|
472fea75b1 | ||
|
|
e1b5438865 | ||
|
|
fbbf523333 | ||
|
|
15fa53d744 | ||
|
|
9595f80fde | ||
|
|
61a67e45c1 | ||
|
|
143e690dab | ||
|
|
ebd507f143 | ||
|
|
f71bc0a8f7 | ||
|
|
edc0c72464 | ||
|
|
c3ce4f718b | ||
|
|
40ee482973 | ||
|
|
a5d2e6fdd2 | ||
|
|
e9a835a642 | ||
|
|
ac7b95ceb6 | ||
|
|
055b2d8ede | ||
|
|
a75cd0a0f5 | ||
|
|
2b1ddf9a4e | ||
|
|
b70442e483 | ||
|
|
798b0fab15 | ||
|
|
e8630132d7 | ||
|
|
273ae5f21a | ||
|
|
06b688fef4 | ||
|
|
59cc038ab2 | ||
|
|
ea5a6c3438 | ||
|
|
e2adfdf3cf | ||
|
|
5ee6531627 | ||
|
|
1cf8ee09e1 | ||
|
|
4a2cf9d722 | ||
|
|
6a6a32c1cf | ||
|
|
ce03611b52 | ||
|
|
bbe9f83878 | ||
|
|
40e6c6aa92 | ||
|
|
9f6ccf092a | ||
|
|
d4ba4b082f | ||
|
|
cce43829eb | ||
|
|
c1bfbac2fe | ||
|
|
fc87cde668 | ||
|
|
400c413029 | ||
|
|
0e6eb45732 | ||
|
|
7ca2028c19 | ||
|
|
08144b54a7 | ||
|
|
103137498b | ||
|
|
8e57e3709d | ||
|
|
bd6bd66946 | ||
|
|
6973dd0ec2 | ||
|
|
2e8b08cd9e | ||
|
|
edc8f43f42 | ||
|
|
133814cd73 | ||
|
|
57213ee31b | ||
|
|
b4fa2a394b | ||
|
|
0c39342d53 | ||
|
|
1c95e8e25c | ||
|
|
ff5a92772b | ||
|
|
bc2a17f70f | ||
|
|
f2e909e578 | ||
|
|
c3385a808c | ||
|
|
8d480331ff | ||
|
|
5216f0989c | ||
|
|
4238553a2e | ||
|
|
79662d0dcf | ||
|
|
ff095bc53d | ||
|
|
eefd70b2de | ||
|
|
9b3eab67a2 | ||
|
|
54def2deb7 | ||
|
|
cd12744726 | ||
|
|
616fd9570f | ||
|
|
0544011ee0 | ||
|
|
51920c7045 | ||
|
|
6f417a1775 | ||
|
|
51034a24c6 | ||
|
|
f631f219b0 | ||
|
|
aacae020b8 | ||
|
|
7c426e0eec | ||
|
|
363826502f | ||
|
|
1cfadae068 | ||
|
|
d3b3d722b4 | ||
|
|
a82428958b | ||
|
|
b185632bda | ||
|
|
e7a0c3d25b | ||
|
|
ab33de2c15 | ||
|
|
a1031cdc27 | ||
|
|
e3ab3ca506 | ||
|
|
b4cd1ccbee | ||
|
|
14573a5714 | ||
|
|
96150a9668 | ||
|
|
c10c1303ed | ||
|
|
7852e69685 | ||
|
|
094880af50 | ||
|
|
c3db518aca | ||
|
|
41ee0c5fdb | ||
|
|
ae178bc146 | ||
|
|
a2a660d483 | ||
|
|
8684cc34f7 | ||
|
|
0aba8b78ba | ||
|
|
5e735aa8d5 | ||
|
|
a2d6338400 | ||
|
|
5e4b8350ab | ||
|
|
4a65b646df | ||
|
|
24c715aae9 | ||
|
|
9ab85768bd | ||
|
|
16458d96e7 | ||
|
|
b307dd17af | ||
|
|
313552352c | ||
|
|
543ac7c649 | ||
|
|
dacff76694 | ||
|
|
c134169ea1 | ||
|
|
e252945047 | ||
|
|
f8cfcaab20 | ||
|
|
528237a239 | ||
|
|
91aefe25c4 | ||
|
|
a8c11097d9 | ||
|
|
92ba4a3ae9 | ||
|
|
7c5d382010 | ||
|
|
f8fbaefd86 | ||
|
|
181b37296a | ||
|
|
2ab61cfa12 | ||
|
|
1b0e9b14ef | ||
|
|
2aacc9266f | ||
|
|
d648d477bb | ||
|
|
6f91c1a1d3 | ||
|
|
aa1aca24b7 | ||
|
|
6a0867172f | ||
|
|
f025a4b2fb | ||
|
|
8871f36a92 | ||
|
|
f17490edad | ||
|
|
b360e782c6 | ||
|
|
8d94324dd6 | ||
|
|
2818609412 | ||
|
|
270a2e69d4 | ||
|
|
d1d9762e29 | ||
|
|
1666e8b127 | ||
|
|
08dfad208b | ||
|
|
b5921f95f3 | ||
|
|
2063bc3db3 | ||
|
|
4380016d52 | ||
|
|
5e3ceabe46 | ||
|
|
8e7936275b | ||
|
|
4b967239fa | ||
|
|
92945c384c | ||
|
|
79d93c4ecf | ||
|
|
76b6f25b0c | ||
|
|
aadce4890a | ||
|
|
0e95a98fc2 | ||
|
|
9483437e8f | ||
|
|
59542f8aef | ||
|
|
e29f7332f5 | ||
|
|
f8640feafe | ||
|
|
e94e9e2be4 | ||
|
|
4053aac365 | ||
|
|
a5fa6c7aef | ||
|
|
97263894d1 | ||
|
|
1885580958 | ||
|
|
1167b470bb | ||
|
|
7600506d6d | ||
|
|
86bad866a0 | ||
|
|
2f1a15cf7e | ||
|
|
52b0e1870f | ||
|
|
9b181c1e0d | ||
|
|
100f2dc45e | ||
|
|
b247c3d477 | ||
|
|
76ee82b258 | ||
|
|
e8fcd29669 | ||
|
|
8dd16ecea4 | ||
|
|
e9c0bcd877 | ||
|
|
75ed4b52a6 | ||
|
|
71635c00df | ||
|
|
1810af5483 | ||
|
|
b07835dfd5 | ||
|
|
4c33aa2aae | ||
|
|
3c255640cb | ||
|
|
3d08ba9ebc | ||
|
|
f64482500e | ||
|
|
215902f192 | ||
|
|
3e9c3a069d | ||
|
|
841fb48479 | ||
|
|
df8e41925f | ||
|
|
6b0994a990 | ||
|
|
7dd616e891 | ||
|
|
c672edbe4d | ||
|
|
687ecc7097 | ||
|
|
b8882b4826 | ||
|
|
51de0b38a4 | ||
|
|
e0309c0482 | ||
|
|
5dbe86869d | ||
|
|
14a11279c7 | ||
|
|
df0ce42377 | ||
|
|
4c03411405 | ||
|
|
f020e18238 | ||
|
|
629bd4aff9 | ||
|
|
f20825a66c | ||
|
|
f098ca0d02 | ||
|
|
1f96f74f4d | ||
|
|
7a3a98c27a | ||
|
|
1130448cb9 | ||
|
|
d388e99c0e | ||
|
|
2baea9a6b4 | ||
|
|
0629625a9a | ||
|
|
a2d0acc761 | ||
|
|
28a6bce90f | ||
|
|
9058192ffe | ||
|
|
465b358271 | ||
|
|
7de585fe1d | ||
|
|
8479730c95 | ||
|
|
7102e2df4c | ||
|
|
c3bd99ff93 | ||
|
|
c560405a46 | ||
|
|
0c0fb28ccc | ||
|
|
a33fce942c | ||
|
|
369cd40ee5 | ||
|
|
577b431a41 | ||
|
|
75cf8acd33 | ||
|
|
d70983962b | ||
|
|
ff440984b0 | ||
|
|
c631155be7 | ||
|
|
6038930755 | ||
|
|
9edc119c62 | ||
|
|
269a94bf03 | ||
|
|
7f3e4d7468 | ||
|
|
eb89891cdd | ||
|
|
038b068370 | ||
|
|
d2017a59de | ||
|
|
3435b56a84 | ||
|
|
a812558d2d | ||
|
|
aefd85455e | ||
|
|
e42f1347b7 | ||
|
|
c7442a03d1 | ||
|
|
1eda8bdd9d | ||
|
|
c4d0628bdb | ||
|
|
d51ae66242 | ||
|
|
121dde6b8b | ||
|
|
98081b067d | ||
|
|
8cc9345b42 | ||
|
|
f7528365b0 | ||
|
|
7baa1a345e | ||
|
|
acf7f4fd52 | ||
|
|
f43e8680b8 | ||
|
|
545a6c1b36 | ||
|
|
f01fd8c850 | ||
|
|
c9ec69b0b5 | ||
|
|
3640e2c5f0 | ||
|
|
b3659cb456 | ||
|
|
76284a2916 | ||
|
|
40b1e011bd | ||
|
|
e0bebecd59 | ||
|
|
8ac0cf6831 | ||
|
|
992163206d | ||
|
|
86dd5d8078 | ||
|
|
932aa9d052 | ||
|
|
5f7f5204ec | ||
|
|
a154d23637 | ||
|
|
ac2bb9d362 | ||
|
|
b918958bfa | ||
|
|
215df4ffa6 | ||
|
|
bb28bc5875 | ||
|
|
a82bc3f712 | ||
|
|
b3a507014b | ||
|
|
49c5e35a14 | ||
|
|
869ed33bd4 | ||
|
|
0c4a9be482 | ||
|
|
4410ec575a | ||
|
|
e3b92fc948 | ||
|
|
4ca4692a67 | ||
|
|
c1284d3c23 | ||
|
|
c8c0eadf72 | ||
|
|
f4bbc8abc7 | ||
|
|
a0f6ea57f8 | ||
|
|
88d21a07ac | ||
|
|
88e3a606a0 | ||
|
|
fff693c3f0 | ||
|
|
1e8d792d39 | ||
|
|
dfb149ac6a | ||
|
|
b5cfc92261 | ||
|
|
079f3e3868 | ||
|
|
15a6d58785 | ||
|
|
a404498f8a | ||
|
|
0d133e2df6 | ||
|
|
488b28bfd5 | ||
|
|
0fceb7b2e1 | ||
|
|
a79d1a98e7 | ||
|
|
43434fd445 | ||
|
|
492e523884 | ||
|
|
3d1fdb7a2b | ||
|
|
95a4bf0ec7 | ||
|
|
0d4f261e14 | ||
|
|
e96288b41b | ||
|
|
deda1abcf7 | ||
|
|
ee79d75483 | ||
|
|
0e9e445ddf | ||
|
|
e64720bcd9 | ||
|
|
6e27590b57 | ||
|
|
916c3c7a2a | ||
|
|
8a5fde8ceb | ||
|
|
f5bd9bde7f | ||
|
|
b987f61924 | ||
|
|
482b51a2f9 | ||
|
|
93f2078eda | ||
|
|
158ae11e61 | ||
|
|
d282388266 | ||
|
|
6ecdfcba38 | ||
|
|
88dc8a547e | ||
|
|
58e24b3c11 | ||
|
|
5f1b3a2284 | ||
|
|
31be2584f2 | ||
|
|
a2311e5128 | ||
|
|
e94d42187b | ||
|
|
2b99cc3f62 | ||
|
|
cb7f782893 | ||
|
|
d5a0f8a74b | ||
|
|
2ebd71df24 | ||
|
|
479203f47c | ||
|
|
022b9209d9 | ||
|
|
771c2c868f | ||
|
|
5285a728b1 | ||
|
|
41e6583920 | ||
|
|
cbb60b3a05 | ||
|
|
cf1d1e3557 | ||
|
|
8f05ee7d79 | ||
|
|
641bd07c0b | ||
|
|
7d9dc0a853 | ||
|
|
e0a46be1b7 | ||
|
|
fd82c67b56 | ||
|
|
f0a83b6f19 | ||
|
|
736b45bb46 | ||
|
|
8eae9b7cb7 | ||
|
|
0aa2d2c613 | ||
|
|
ad43db10f2 | ||
|
|
606f507422 | ||
|
|
36b7778883 | ||
|
|
7b032a6a73 | ||
|
|
0e00237e44 | ||
|
|
e9ec9a7d7f | ||
|
|
6834507f3a | ||
|
|
90e99255b1 | ||
|
|
b6487000a3 | ||
|
|
18ce2f72ed | ||
|
|
8a2d04bf69 | ||
|
|
a210ffecec | ||
|
|
aff96e8144 | ||
|
|
3d4c639bb4 | ||
|
|
d507e79505 | ||
|
|
d3e242ff42 | ||
|
|
df7616403d | ||
|
|
962b15517a | ||
|
|
d295f2391f | ||
|
|
c042222eea | ||
|
|
63f6596bc2 | ||
|
|
d8a8aba0ea | ||
|
|
d9d9682029 | ||
|
|
4a27a8ac70 | ||
|
|
32857ff304 | ||
|
|
375bfd3862 | ||
|
|
9430c41b8a | ||
|
|
9b342e146a | ||
|
|
e5685f2959 | ||
|
|
4150feece2 | ||
|
|
6879ec5deb | ||
|
|
28ad00ffad | ||
|
|
bf51049fbf | ||
|
|
36189e9122 | ||
|
|
4c747463ac | ||
|
|
b4b1fbb9e6 | ||
|
|
209eaff3c6 | ||
|
|
c3f70703fd | ||
|
|
f2967e7b94 | ||
|
|
77c46b5c43 | ||
|
|
5fda5bc081 | ||
|
|
33a7b38c6a | ||
|
|
2a7c0bd58c | ||
|
|
86e4d0db0f | ||
|
|
1310fea64d | ||
|
|
382f5a5bb9 | ||
|
|
ff3303e312 | ||
|
|
6ce0ef62e9 | ||
|
|
2a03341fb6 | ||
|
|
77a55dde56 | ||
|
|
1a71cdff4a | ||
|
|
63f65c51c3 | ||
|
|
75de2526c1 | ||
|
|
6fc4b7b120 | ||
|
|
7b8068139b | ||
|
|
ced7de884f | ||
|
|
bc51c9f25b | ||
|
|
c412e8b9a7 | ||
|
|
4e0ff224b6 | ||
|
|
61c817c9cb | ||
|
|
2ed384f677 | ||
|
|
7a851cb080 | ||
|
|
13aa889633 | ||
|
|
5c3fba5f41 | ||
|
|
145d9216bf | ||
|
|
84f46de940 | ||
|
|
cb9a5b6fbe | ||
|
|
d9718faba4 | ||
|
|
5472ff41f0 | ||
|
|
4f94c3b310 | ||
|
|
420f1efa50 | ||
|
|
5d2ce17817 | ||
|
|
053cb823a1 | ||
|
|
18a7992372 | ||
|
|
9e935f5bfb | ||
|
|
9f49e24dc5 | ||
|
|
dbf60f16bc | ||
|
|
0f3a228788 | ||
|
|
d905f5b095 | ||
|
|
1c310486c7 | ||
|
|
4b01c6da91 | ||
|
|
5782378616 | ||
|
|
64c97ebfba | ||
|
|
5fd4d56b00 | ||
|
|
e658b5167e | ||
|
|
cea698d720 | ||
|
|
c07f41c312 | ||
|
|
a837aa0334 | ||
|
|
0050724e22 | ||
|
|
adac4ac75c | ||
|
|
718f37024a | ||
|
|
fcb3008539 | ||
|
|
8faf3eec53 | ||
|
|
2bc3df3255 | ||
|
|
5b0e550c85 | ||
|
|
e52211abf2 | ||
|
|
9b6f231b34 | ||
|
|
b71223705f | ||
|
|
863fbe69bb | ||
|
|
2d46279961 | ||
|
|
0d0207d77f | ||
|
|
00bbade34f | ||
|
|
682f741ddc | ||
|
|
3d2744c9e3 | ||
|
|
cc286dcf16 | ||
|
|
27c6e2a7bd | ||
|
|
72c7a67ad5 | ||
|
|
8fe5e4e238 | ||
|
|
02f23d0c62 | ||
|
|
ff6f4d4152 | ||
|
|
2242f46792 | ||
|
|
642b5609b2 | ||
|
|
98878f3e7c | ||
|
|
3eb28deccf | ||
|
|
761a852156 | ||
|
|
f4ddb11c1f | ||
|
|
75158c11ea | ||
|
|
fe96706b0c | ||
|
|
b87482e824 | ||
|
|
a9ba99dc79 | ||
|
|
8884e92a1a | ||
|
|
6385514257 | ||
|
|
d3ad47022b | ||
|
|
138d4b507d | ||
|
|
3c0b195bcf | ||
|
|
d941a71bb5 | ||
|
|
08697d9daf | ||
|
|
8959871988 | ||
|
|
bb43a04992 | ||
|
|
5f93dc7991 | ||
|
|
9be8eb223c | ||
|
|
e8b6c47e0f | ||
|
|
697d442afb | ||
|
|
5dbd261b5a | ||
|
|
9bc94ca658 | ||
|
|
4404b5f849 | ||
|
|
6a4b73b8a9 | ||
|
|
b6146224b3 | ||
|
|
e3593c1b0c | ||
|
|
90a2f10da6 | ||
|
|
60bab1c004 | ||
|
|
0898940d0b | ||
|
|
38b65b0ca4 | ||
|
|
d36ce5eefc | ||
|
|
ff99ab9cfe | ||
|
|
64995367c3 | ||
|
|
c67f0ffc11 | ||
|
|
d5403ae112 | ||
|
|
d21d64cbfe | ||
|
|
347ee3c4f5 | ||
|
|
77ed1cca29 | ||
|
|
cfac7dbb37 | ||
|
|
f27d72f3f9 | ||
|
|
3cd93b287e | ||
|
|
5e5605881b | ||
|
|
a9b48610df | ||
|
|
3cca09a48c | ||
|
|
3134448eac | ||
|
|
663bdf945b | ||
|
|
e94d1175e7 | ||
|
|
e20b761965 | ||
|
|
90c64812d0 | ||
|
|
08d368fc49 | ||
|
|
39385f0bff | ||
|
|
8bc3418ce1 | ||
|
|
a145700398 | ||
|
|
409535e617 | ||
|
|
f625016efe | ||
|
|
f4c94ab1d7 | ||
|
|
8234706dd3 | ||
|
|
1a31dc870f | ||
|
|
a1712858c5 | ||
|
|
0059e768b9 | ||
|
|
4fe1550bd2 | ||
|
|
0c182c8a7f | ||
|
|
bcdf746def | ||
|
|
bc13ac3a98 | ||
|
|
a894e018cd | ||
|
|
cbecfd444d | ||
|
|
357e5ef963 | ||
|
|
60594c9f03 | ||
|
|
44b5bae8da | ||
|
|
2e856196c5 | ||
|
|
8672187c02 | ||
|
|
cf251c45b8 | ||
|
|
385c03096d | ||
|
|
f323f5e3de | ||
|
|
9562ee86cd | ||
|
|
adfb4bc861 | ||
|
|
957232ca40 | ||
|
|
44c9d9aead | ||
|
|
f95245cedd | ||
|
|
3c034adf48 | ||
|
|
abd3c5a06d | ||
|
|
ca4951a475 | ||
|
|
e751a3d307 | ||
|
|
2a8bdfd714 | ||
|
|
be9dca3ee2 | ||
|
|
32707fb501 | ||
|
|
d72547e187 | ||
|
|
9150ddffb1 |
2
.bazelrc
Normal file
2
.bazelrc
Normal file
@@ -0,0 +1,2 @@
|
||||
build --apple_generate_dsym --define=apple.propagate_embedded_extra_outputs=yes
|
||||
build --host_force_python=PY2
|
||||
22
.clang-format
Normal file
22
.clang-format
Normal file
@@ -0,0 +1,22 @@
|
||||
BasedOnStyle: Google
|
||||
Language: Cpp
|
||||
Standard: Cpp11
|
||||
|
||||
# Disable ColumnLimit because it causes some very weird line breaks.
|
||||
# For ObjC the limit is 100
|
||||
# For Cpp the limit is 80
|
||||
ColumnLimit: 0
|
||||
|
||||
# Allow short case statements to be on a single line
|
||||
AllowShortCaseLabelsOnASingleLine: true
|
||||
|
||||
# Ban short loops and functions on a single line
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: false
|
||||
|
||||
# Allow spaces in NSArray/NSDictionary literals @[ and @{
|
||||
SpacesInContainerLiterals: true
|
||||
|
||||
# For pointers, always put the * next to the variable name.
|
||||
DerivePointerAlignment: false
|
||||
PointerAlignment: Right
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,6 +1,7 @@
|
||||
.DS_Store
|
||||
Build
|
||||
Dist
|
||||
default.profraw
|
||||
*.provisionprofile
|
||||
bazel-*
|
||||
Pods
|
||||
Santa.xcodeproj/xcuserdata
|
||||
Santa.xcodeproj/project.xcworkspace
|
||||
|
||||
13
.travis.yml
13
.travis.yml
@@ -1,8 +1,15 @@
|
||||
---
|
||||
os: osx
|
||||
osx_image: xcode11
|
||||
language: objective-c
|
||||
sudo: false
|
||||
|
||||
before_install:
|
||||
- gem install cocoapods xcpretty
|
||||
addons:
|
||||
homebrew:
|
||||
taps: bazelbuild/tap
|
||||
packages: bazelbuild/tap/bazel
|
||||
update: true
|
||||
|
||||
script:
|
||||
- xcodebuild -workspace Santa.xcworkspace -scheme All build test CODE_SIGN_IDENTITY='' | xcpretty -sc && exit ${PIPESTATUS[0]}
|
||||
- bazel build :release --show_progress_rate_limit=30.0 -c opt --apple_generate_dsym --color=no --verbose_failures --sandbox_debug
|
||||
- bazel test :unit_tests --show_progress_rate_limit=30.0 --test_output=errors --color=no --verbose_failures --sandbox_debug
|
||||
|
||||
167
BUILD
Normal file
167
BUILD
Normal file
@@ -0,0 +1,167 @@
|
||||
load("@build_bazel_rules_apple//apple:versioning.bzl", "apple_bundle_version")
|
||||
load("//:helper.bzl", "run_command")
|
||||
load("//:version.bzl", "SANTA_VERSION")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
exports_files(["LICENSE"])
|
||||
|
||||
# The version label for mac_* rules.
|
||||
apple_bundle_version(
|
||||
name = "version",
|
||||
build_version = SANTA_VERSION,
|
||||
short_version_string = SANTA_VERSION,
|
||||
)
|
||||
|
||||
# Used to detect optimized builds
|
||||
config_setting(
|
||||
name = "opt_build",
|
||||
values = {"compilation_mode": "opt"},
|
||||
)
|
||||
|
||||
################################################################################
|
||||
# Loading/Unloading/Reloading
|
||||
################################################################################
|
||||
run_command(
|
||||
name = "unload",
|
||||
cmd = """
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.google.santad.plist 2>/dev/null
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.google.santa.bundleservice.plist 2>/dev/null
|
||||
sudo kextunload -b com.google.santa-driver 2>/dev/null
|
||||
launchctl unload /Library/LaunchAgents/com.google.santa.plist 2>/dev/null
|
||||
""",
|
||||
)
|
||||
|
||||
run_command(
|
||||
name = "load",
|
||||
cmd = """
|
||||
sudo launchctl load /Library/LaunchDaemons/com.google.santad.plist
|
||||
sudo launchctl load /Library/LaunchDaemons/com.google.santa.bundleservice.plist
|
||||
launchctl load /Library/LaunchAgents/com.google.santa.plist
|
||||
""",
|
||||
)
|
||||
|
||||
run_command(
|
||||
name = "reload",
|
||||
srcs = [
|
||||
"//Source/santa:Santa",
|
||||
"//Source/santa_driver",
|
||||
],
|
||||
cmd = """
|
||||
set -e
|
||||
|
||||
rm -rf /tmp/bazel_santa_reload
|
||||
unzip -d /tmp/bazel_santa_reload \
|
||||
$${BUILD_WORKSPACE_DIRECTORY}/bazel-bin/Source/santa_driver/santa_driver.zip >/dev/null
|
||||
unzip -d /tmp/bazel_santa_reload \
|
||||
$${BUILD_WORKSPACE_DIRECTORY}/bazel-bin/Source/santa/Santa.zip >/dev/null
|
||||
echo "You may be asked for your password for sudo"
|
||||
sudo BINARIES=/tmp/bazel_santa_reload CONF=$${BUILD_WORKSPACE_DIRECTORY}/Conf \
|
||||
$${BUILD_WORKSPACE_DIRECTORY}/Conf/install.sh
|
||||
rm -rf /tmp/bazel_santa_reload
|
||||
echo "Time to stop being naughty"
|
||||
""",
|
||||
)
|
||||
|
||||
################################################################################
|
||||
# Release rules - used to create a release tarball
|
||||
################################################################################
|
||||
genrule(
|
||||
name = "release",
|
||||
srcs = [
|
||||
"//Source/santa:Santa",
|
||||
"//Source/santa_driver",
|
||||
"Conf/install.sh",
|
||||
"Conf/uninstall.sh",
|
||||
"Conf/com.google.santa.bundleservice.plist",
|
||||
"Conf/com.google.santad.plist",
|
||||
"Conf/com.google.santa.plist",
|
||||
"Conf/com.google.santa.asl.conf",
|
||||
"Conf/com.google.santa.newsyslog.conf",
|
||||
"Conf/Package/Makefile",
|
||||
"Conf/Package/postinstall",
|
||||
"Conf/Package/preinstall",
|
||||
],
|
||||
outs = ["santa-" + SANTA_VERSION + ".tar.gz"],
|
||||
cmd = select({
|
||||
"//conditions:default": """
|
||||
echo "ERROR: Trying to create a release tarball without optimization."
|
||||
echo "Please add '-c opt' flag to bazel invocation"
|
||||
""",
|
||||
":opt_build": """
|
||||
# Extract santa_driver.zip and Santa.zip
|
||||
for SRC in $(SRCS); do
|
||||
if [ "$$(basename $${SRC})" == "santa_driver.zip" -o "$$(basename $${SRC})" == "Santa.zip" ]; then
|
||||
mkdir -p $(@D)/binaries
|
||||
unzip -q $${SRC} -d $(@D)/binaries >/dev/null
|
||||
fi
|
||||
done
|
||||
|
||||
# Copy config files
|
||||
for SRC in $(SRCS); do
|
||||
if [[ "$$(dirname $${SRC})" == *"Conf" ]]; then
|
||||
mkdir -p $(@D)/conf
|
||||
cp $${SRC} $(@D)/conf/
|
||||
fi
|
||||
done
|
||||
|
||||
# Gather together the dSYMs. Throw an error if no dSYMs were found
|
||||
for SRC in $(SRCS); do
|
||||
case $${SRC} in
|
||||
*santa-driver.kext.dSYM*Info.plist)
|
||||
mkdir -p $(@D)/dsym
|
||||
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santa-driver.kext.dSYM
|
||||
;;
|
||||
*santad.dSYM*Info.plist)
|
||||
mkdir -p $(@D)/dsym
|
||||
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santad.dSYM
|
||||
;;
|
||||
*santactl.dSYM*Info.plist)
|
||||
mkdir -p $(@D)/dsym
|
||||
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santactl.dSYM
|
||||
;;
|
||||
*santabundleservice.dSYM*Info.plist)
|
||||
mkdir -p $(@D)/dsym
|
||||
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santabundleservice.dSYM
|
||||
;;
|
||||
*Santa.app.dSYM*Info.plist)
|
||||
mkdir -p $(@D)/dsym
|
||||
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/Santa.app.dSYM
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Cause a build failure if the dSYMs are missing.
|
||||
if [[ ! -d "$(@D)/dsym" ]]; then
|
||||
echo "dsym dir missing: Did you forget to use --apple_generate_dsym?"
|
||||
echo "This flag is required for the 'release' target."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Update all the timestamps to now. Bazel avoids timestamps to allow
|
||||
# builds to be hermetic and cacheable but for releases we want the
|
||||
# timestamps to be more-or-less correct.
|
||||
find $(@D)/{binaries,conf,dsym} -exec touch {} \\;
|
||||
|
||||
# Create final output tar
|
||||
tar -C $(@D) -czpf $(@) binaries dsym conf
|
||||
""",
|
||||
}),
|
||||
heuristic_label_expansion = 0,
|
||||
)
|
||||
|
||||
test_suite(
|
||||
name = "unit_tests",
|
||||
tests = [
|
||||
"//Source/common:SNTFileInfoTest",
|
||||
"//Source/common:SNTPrefixTreeTest",
|
||||
"//Source/santa_driver:SantaCacheTest",
|
||||
"//Source/santactl:SNTCommandFileInfoTest",
|
||||
"//Source/santactl:SNTCommandSyncTest",
|
||||
"//Source/santad:SNTEventTableTest",
|
||||
"//Source/santad:SNTExecutionControllerTest",
|
||||
"//Source/santad:SNTRuleTableTest",
|
||||
],
|
||||
)
|
||||
@@ -29,8 +29,8 @@ rake tests:kernel # only necessary if you're changing the kext code
|
||||
|
||||
All code submissions should try to match the surrounding code. Wherever possible,
|
||||
code should adhere to either the
|
||||
[Google Objective-C Style Guide](http://google-styleguide.googlecode.com/svn/trunk/objcguide.xml)
|
||||
or the [Google C++ Style Guide](http://google-styleguide.googlecode.com/svn/trunk/cppguide.html).
|
||||
[Google Objective-C Style Guide](https://google.github.io/styleguide/objcguide.xml)
|
||||
or the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html).
|
||||
|
||||
### The small print
|
||||
Contributions made by corporations are covered by a different agreement than
|
||||
|
||||
@@ -13,76 +13,67 @@ TITLE:=santa
|
||||
REVERSE_DOMAIN:=com.google
|
||||
|
||||
# Get latest Release version using the GitHub API. Each release is bound to a
|
||||
# git tag, which should always be a semantic version number
|
||||
# git tag, which should always be a semantic version number. The most recent
|
||||
# release is always first in the API result.
|
||||
PACKAGE_VERSION:=$(shell curl -fs https://api.github.com/repos/google/santa/releases |\
|
||||
python -c 'import json, sys; print json.load(sys.stdin)[0]["tag_name"]' 2>/dev/null)
|
||||
|
||||
# Get the download URL for the latest Release using the GitHub API. Each release
|
||||
# should have a single 'asset' which will be a tar.bz2 containing all of the files
|
||||
# associated with that release.
|
||||
# The tarball layout is:
|
||||
# Get the download URL for the latest Release. Each release should have a
|
||||
# tarball named santa-$version.tar.bz2 containing all of the files associated
|
||||
# with that release. The tarball layout is:
|
||||
#
|
||||
# santa-$version.tar.bz2
|
||||
# +--santa-$version
|
||||
# |-- binaries
|
||||
# | |-- santa-driver.kext
|
||||
# | |-- Santa.app
|
||||
# | |-- santad
|
||||
# | +-- santactl
|
||||
# | |-- santa-driver.kext
|
||||
# | |-- Santa.app
|
||||
# |-- conf
|
||||
# | |-- install.sh
|
||||
# | |-- com.google.santad.plist
|
||||
# | |-- com.google.santasync.plist
|
||||
# | |-- com.google.santagui.plist
|
||||
# | +-- com.google.santa.asl.conf
|
||||
# | +-- com.google.santa.newsyslog.conf
|
||||
# +--dsym
|
||||
# |-- santa-driver.kext.dSYM
|
||||
# |-- Santa.app.dSYM
|
||||
# |-- santad.dSYM
|
||||
# +-- santactl.dSYM
|
||||
#
|
||||
PACKAGE_DOWNLOAD_URL:=$(shell curl -fs https://api.github.com/repos/google/santa/releases |\
|
||||
python -c 'import json, sys; print json.load(sys.stdin)[0]["assets"][0]["browser_download_url"]' 2>/dev/null)
|
||||
# |-- santa-driver.kext.dSYM
|
||||
# |-- Santa.app.dSYM
|
||||
# |-- santad.dSYM
|
||||
# +-- santactl.dSYM
|
||||
PACKAGE_DOWNLOAD_URL:="https://github.com/google/santa/releases/download/${PACKAGE_VERSION}/santa-${PACKAGE_VERSION}.tar.bz2"
|
||||
|
||||
PAYLOAD:=pack-usr-libexec-santad \
|
||||
pack-usr-sbin-santactl \
|
||||
pack-Library-Extensions-santa-driver.kext \
|
||||
PAYLOAD:=pack-Library-Extensions-santa-driver.kext \
|
||||
pack-applications-Santa.app \
|
||||
pack-Library-LaunchDaemons-com.google.santad.plist \
|
||||
pack-Library-LaunchDaemons-com.google.santasync.plist \
|
||||
pack-Library-LaunchAgents-com.google.santagui.plist \
|
||||
pack-etc-asl-com.google.santa.asl.conf \
|
||||
pack-etc-newsyslog.d-com.google.santa.newsyslog.conf \
|
||||
pack-script-preinstall \
|
||||
pack-script-postinstall
|
||||
|
||||
santad: download
|
||||
santactl: download
|
||||
santa-driver.kext: download
|
||||
Santa.app: download
|
||||
com.google.santad.plist: download
|
||||
com.google.santagui.plist: download
|
||||
com.google.santasync.plist: download
|
||||
com.google.santa.asl.conf: download
|
||||
com.google.santa.newsyslog.conf: download
|
||||
|
||||
download:
|
||||
$(if $(PACKAGE_VERSION),, $(error GitHub API returned unexpected result. Wait a while and try again))
|
||||
$(if $(PACKAGE_DOWNLOAD_URL),, $(error GitHub API returned unexpected result. Wait a while and try again))
|
||||
|
||||
@curl-fL ${PACKAGE_DOWNLOAD_URL} | tar xvj --strip=2
|
||||
@curl -fL ${PACKAGE_DOWNLOAD_URL} | tar xvj --strip=2
|
||||
@rm -rf *.dSYM
|
||||
|
||||
pack-usr-libexec-santad: santad l_usr
|
||||
@sudo mkdir -p ${WORK_D}/usr/libexec
|
||||
@sudo chown root:wheel ${WORK_D}/usr/libexec
|
||||
@sudo chmod 755 ${WORK_D}/usr/libexec
|
||||
@sudo install -m 755 -o root -g wheel santad ${WORK_D}/usr/libexec
|
||||
|
||||
pack-etc-asl-com.google.santa.asl.conf: com.google.santa.asl.conf l_private_etc
|
||||
@sudo mkdir -p ${WORK_D}/private/etc/asl
|
||||
@sudo chown root:wheel ${WORK_D}/private/etc/asl
|
||||
@sudo chmod 755 ${WORK_D}/private/etc/asl
|
||||
@sudo install -m 644 -o root -g wheel com.google.santa.asl.conf ${WORK_D}/private/etc/asl
|
||||
|
||||
pack-etc-newsyslog.d-com.google.santa.newsyslog.conf: com.google.santa.newsyslog.conf l_private_etc
|
||||
@sudo mkdir -p ${WORK_D}/private/etc/newsyslog.d
|
||||
@sudo chown root:wheel ${WORK_D}/private/etc/newsyslog.d
|
||||
@sudo chmod 755 ${WORK_D}/private/etc/newsyslog.d
|
||||
@sudo install -m 644 -o root -g wheel com.google.santa.newsyslog.conf ${WORK_D}/private/etc/newsyslog.d
|
||||
|
||||
pack-Library-Extensions-santa-driver.kext: santa-driver.kext l_Library
|
||||
@sudo mkdir -p ${WORK_D}/Library/Extensions
|
||||
@sudo ${DITTO} --noqtn santa-driver.kext ${WORK_D}/Library/Extensions/santa-driver.kext
|
||||
@@ -95,11 +86,10 @@ myclean:
|
||||
@rm -rf *.dSYM
|
||||
@rm -rf Santa.app
|
||||
@rm -rf santa-driver.kext
|
||||
@rm -f santad
|
||||
@rm -f santactl
|
||||
@rm -f config.plist
|
||||
@rm -f com.google.santa.asl.conf
|
||||
@rm -f com.google.santa.newsyslog.conf
|
||||
@rm -f com.google.santad.plist
|
||||
@rm -f com.google.santagui.plist
|
||||
@rm -f com.google.santasync.plist
|
||||
@rm -f install.sh
|
||||
@rm -f uninstall.sh
|
||||
|
||||
@@ -9,17 +9,28 @@
|
||||
# Restart syslogd to pick up ASL configuration change
|
||||
/usr/bin/killall -HUP syslogd
|
||||
|
||||
/sbin/kextload /Library/Extensions/santa-driver.kext
|
||||
# Create hopefully useful symlink for santactl
|
||||
mkdir -p /usr/local/bin
|
||||
/bin/ln -sf /Applications/Santa.app/Contents/MacOS/santactl /usr/local/bin/santactl
|
||||
|
||||
sleep 1
|
||||
if [ $(uname -r | cut -d'.' -f1) -ge 19 ]; then
|
||||
# Running on 10.15+
|
||||
echo "Santa postinstall: running on 10.15+"
|
||||
/bin/rm -rf /Library/Extensions/santa-driver.kext
|
||||
/bin/rm -f /Library/LaunchDaemons/com.google.santad.plist
|
||||
else
|
||||
# Running on <10.15
|
||||
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santad.plist
|
||||
fi
|
||||
|
||||
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santad.plist
|
||||
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santasync.plist
|
||||
|
||||
sleep 1
|
||||
# Load the bundle service
|
||||
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santa.bundleservice.plist
|
||||
|
||||
user=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
[[ -z "$user" ]] && exit 0
|
||||
/bin/launchctl asuser ${user} /bin/launchctl load /Library/LaunchAgents/com.google.santagui.plist
|
||||
if [[ -z "$user" ]]; then
|
||||
/Applications/Santa.app/Contents/MacOS/Santa --load-system-extension
|
||||
exit 0
|
||||
fi
|
||||
/bin/launchctl asuser ${user} /bin/launchctl load /Library/LaunchAgents/com.google.santa.plist
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -6,16 +6,24 @@
|
||||
|
||||
[[ $3 != "/" ]] && exit 0
|
||||
|
||||
/bin/launchctl remove com.google.santad
|
||||
/bin/launchctl remove com.google.santad || true
|
||||
/bin/launchctl remove com.google.santa.bundleservice || true
|
||||
|
||||
/bin/sleep 1
|
||||
|
||||
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1 || true
|
||||
|
||||
# Remove cruft from old Santa versions
|
||||
/bin/rm -f /usr/libexec/santad
|
||||
/bin/rm -f /usr/sbin/santactl
|
||||
/bin/launchctl remove com.google.santasync
|
||||
/bin/rm -f /Library/LaunchDaemons/com.google.santasync.plist
|
||||
/bin/rm -rf /Applications/Santa.app
|
||||
|
||||
sleep 1
|
||||
|
||||
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
|
||||
|
||||
sleep 1
|
||||
/bin/sleep 1
|
||||
|
||||
user=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
[[ -n "$user" ]] && /bin/launchctl asuser ${user} /bin/launchctl remove com.google.santagui
|
||||
[[ -n "$user" ]] && /bin/launchctl asuser ${user} /bin/launchctl remove com.google.santa
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# Copy this file to /etc/asl to log all messages from santa-driver to the log file
|
||||
? [S= Message santa-driver:] claim only
|
||||
? [S= Message santa-driver:] file /var/log/santa.log format="[$((Time)(utc.3))] $Message"
|
||||
> /var/log/santa.log mode=0644 rotate=seq compress file_max=5M all_max=100M
|
||||
> /var/db/santa/santa.log format="[$((Time)(ISO8601Z.3))] $Message" mode=0644 rotate=seq compress file_max=25M all_max=100M uid=0 gid=0
|
||||
? [= Sender kernel] [S= Message santa-driver:] claim
|
||||
? [= Sender kernel] [S= Message santa-driver:] file /var/db/santa/santa.log
|
||||
? [= Facility com.google.santa] claim
|
||||
? [= Facility com.google.santa] file /var/db/santa/santa.log
|
||||
|
||||
26
Conf/com.google.santa.bundleservice.plist
Normal file
26
Conf/com.google.santa.bundleservice.plist
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.google.santa.bundleservice</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/Applications/Santa.app/Contents/MacOS/santabundleservice</string>
|
||||
<string>--syslog</string>
|
||||
</array>
|
||||
<key>MachServices</key>
|
||||
<dict>
|
||||
<key>com.google.santa.bundleservice</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>RunAtLoad</key>
|
||||
<false/>
|
||||
<key>KeepAlive</key>
|
||||
<false/>
|
||||
<key>ProcessType</key>
|
||||
<string>Interactive</string>
|
||||
<key>ThrottleInterval</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</plist>
|
||||
2
Conf/com.google.santa.newsyslog.conf
Normal file
2
Conf/com.google.santa.newsyslog.conf
Normal file
@@ -0,0 +1,2 @@
|
||||
# logfilename [owner:group] mode count size(KiB) when flags [/pid_file] # [sig_num]
|
||||
/var/db/santa/santa.log root:wheel 644 10 25000 * NZ
|
||||
@@ -3,12 +3,15 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.google.santagui</string>
|
||||
<string>com.google.santa</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/Applications/Santa.app/Contents/MacOS/Santa</string>
|
||||
<string>--syslog</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,29 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.google.santad</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/usr/libexec/santad</string>
|
||||
</array>
|
||||
<key>MachServices</key>
|
||||
<dict>
|
||||
<key>SantaXPCNotifications</key>
|
||||
<true/>
|
||||
<key>SantaXPCControl</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>StandardOutPath</key>
|
||||
<string>/var/log/santa.log</string>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>/var/log/santa.log</string>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true />
|
||||
<key>ProcessType</key>
|
||||
<string>Interactive</string>
|
||||
<key>Label</key>
|
||||
<string>com.google.santad</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/Applications/Santa.app/Contents/Library/SystemExtensions/com.google.santa.daemon.systemextension/Contents/MacOS/com.google.santa.daemon</string>
|
||||
<string>--syslog</string>
|
||||
</array>
|
||||
<key>MachServices</key>
|
||||
<dict>
|
||||
<key>com.google.santa.daemon</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>ProcessType</key>
|
||||
<string>Interactive</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.google.santasync</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/usr/sbin/santactl</string>
|
||||
<string>sync</string>
|
||||
</array>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>/var/log/santa.log</string>
|
||||
<key>ProcessType</key>
|
||||
<string>Background</string>
|
||||
<key>StartInterval</key>
|
||||
<integer>600</integer>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<!-- Minimal Configuration -->
|
||||
<key>ClientMode</key>
|
||||
<integer>1</integer>
|
||||
|
||||
<!-- For documentation of other keys, see the following URL:
|
||||
https://github.com/google/santa/wiki/Configuration-Keys -->
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -5,51 +5,77 @@ if [[ $EUID -ne 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -d "binaries" ]]; then
|
||||
SOURCE="."
|
||||
elif [[ -d "../binaries" ]]; then
|
||||
SOURCE=".."
|
||||
else
|
||||
echo "Can't find binaries, run install.sh from inside the conf directory" 1>&2
|
||||
exit 1
|
||||
if [[ -z "${BINARIES}" || -z "${CONF}" ]]; then
|
||||
if [[ -d "binaries" ]]; then
|
||||
BINARIES="${PWD}/binaries"
|
||||
CONF="${PWD}/conf"
|
||||
elif [[ -d "../binaries" ]]; then
|
||||
BINARIES="${PWD}/../binaries"
|
||||
CONF="${PWD}/../conf"
|
||||
else
|
||||
echo "Can't find binaries, run install.sh from inside the conf directory" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Determine if anyone is logged into the GUI
|
||||
GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
|
||||
# Unload santad and scheduled sync job.
|
||||
/bin/launchctl remove com.google.santad >/dev/null 2>&1
|
||||
/bin/launchctl remove com.google.santasync >/dev/null 2>&1
|
||||
|
||||
# Unload bundle service
|
||||
/bin/launchctl remove com.google.santa.bundleservice >/dev/null 2>&1
|
||||
|
||||
# Unload kext.
|
||||
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
|
||||
|
||||
# Determine if anyone is logged into the GUI
|
||||
GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
|
||||
# Unload GUI agent if someone is logged in.
|
||||
[[ -n "$GUI_USER" ]] && \
|
||||
/bin/launchctl asuser ${GUI_USER} /bin/launchctl remove /Library/LaunchAgents/com.google.santagui.plist
|
||||
/bin/launchctl asuser ${GUI_USER} /bin/launchctl remove com.google.santagui
|
||||
[[ -n "$GUI_USER" ]] && \
|
||||
/bin/launchctl asuser ${GUI_USER} /bin/launchctl remove com.google.santa
|
||||
|
||||
# Cleanup cruft from old versions
|
||||
/bin/launchctl remove com.google.santasync >/dev/null 2>&1
|
||||
/bin/rm /Library/LaunchDaemons/com.google.santasync.plist >/dev/null 2>&1
|
||||
/bin/rm /usr/libexec/santad >/dev/null 2>&1
|
||||
/bin/rm /usr/sbin/santactl >/dev/null 2>&1
|
||||
/bin/rm -rf /Applications/Santa.app 2>&1
|
||||
/bin/rm -rf /Library/Extensions/santa-driver.kext 2>&1
|
||||
|
||||
# Copy new files.
|
||||
/bin/cp ${SOURCE}/binaries/santad /usr/libexec
|
||||
/bin/cp ${SOURCE}/binaries/santactl /usr/sbin
|
||||
/bin/cp -r ${SOURCE}/binaries/santa-driver.kext /Library/Extensions
|
||||
/bin/cp -r ${SOURCE}/binaries/Santa.app /Applications
|
||||
/bin/mkdir -p /var/db/santa
|
||||
|
||||
/bin/cp ${SOURCE}/conf/com.google.{santad,santasync}.plist /Library/LaunchDaemons
|
||||
/bin/cp ${SOURCE}/conf/com.google.santagui.plist /Library/LaunchAgents
|
||||
/bin/cp ${SOURCE}/conf/com.google.santa.asl.conf /etc/asl/
|
||||
/bin/cp -r ${BINARIES}/Santa.app /Applications
|
||||
|
||||
/bin/mkdir -p /usr/local/bin
|
||||
/bin/ln -s /Applications/Santa.app/Contents/MacOS/santactl /usr/local/bin 2>/dev/null
|
||||
|
||||
/bin/cp ${CONF}/com.google.santa.plist /Library/LaunchAgents
|
||||
/bin/cp ${CONF}/com.google.santa.bundleservice.plist /Library/LaunchDaemons
|
||||
/bin/cp ${CONF}/com.google.santa.asl.conf /etc/asl/
|
||||
/bin/cp ${CONF}/com.google.santa.newsyslog.conf /etc/newsyslog.d/
|
||||
|
||||
# Reload syslogd to pick up ASL configuration change.
|
||||
/usr/bin/killall -HUP syslogd
|
||||
|
||||
# Load kext.
|
||||
/sbin/kextload /Library/Extensions/santa-driver.kext
|
||||
# Only copy the kext and load santad if running pre-10.15
|
||||
if [ $(uname -r | cut -d'.' -f1) -lt 19 ]; then
|
||||
/bin/cp -r ${BINARIES}/santa-driver.kext /Library/Extensions
|
||||
/bin/cp ${CONF}/com.google.santad.plist /Library/LaunchDaemons
|
||||
/bin/launchctl load /Library/LaunchDaemons/com.google.santad.plist
|
||||
else
|
||||
/Applications/Santa.app/Contents/MacOS/Santa --load-system-extension
|
||||
fi
|
||||
|
||||
# Load santad and scheduled sync jobs.
|
||||
/bin/launchctl load /Library/LaunchDaemons/com.google.santad.plist
|
||||
/bin/launchctl load /Library/LaunchDaemons/com.google.santasync.plist
|
||||
# Load the bundle service
|
||||
/bin/launchctl load /Library/LaunchDaemons/com.google.santa.bundleservice.plist
|
||||
|
||||
# Load GUI agent if someone is logged in.
|
||||
[[ -n "$GUI_USER" ]] && \
|
||||
/bin/launchctl asuser ${GUI_USER} /bin/launchctl load /Library/LaunchAgents/com.google.santagui.plist
|
||||
if [[ -n "$GUI_USER" ]]; then
|
||||
/bin/launchctl asuser ${GUI_USER} \
|
||||
/bin/launchctl load -w /Library/LaunchAgents/com.google.santa.plist
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
||||
34
Conf/uninstall.sh
Executable file
34
Conf/uninstall.sh
Executable file
@@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Uninstalls Santa from the boot volume, clearing up everything but logs/configs.
|
||||
# Unloads the kernel extension, services, and deletes component files.
|
||||
# If a user is logged in, also unloads the GUI agent.
|
||||
|
||||
[ "$EUID" != 0 ] && printf "%s\n" "This requires running as root/sudo." && exit 1
|
||||
|
||||
# For macOS 10.15+ this will block up to 60 seconds
|
||||
/Applications/Santa.app/Contents/MacOS/Santa --unload-system-extension
|
||||
|
||||
/bin/launchctl remove com.google.santad
|
||||
sleep 1
|
||||
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
|
||||
user=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
[[ -n "$user" ]] && /bin/launchctl asuser ${user} /bin/launchctl remove com.google.santagui
|
||||
[[ -n "$user" ]] && /bin/launchctl asuser ${user} /bin/launchctl remove com.google.santa
|
||||
# and to clean out the log config, although it won't write after wiping the binary
|
||||
/usr/bin/killall -HUP syslogd
|
||||
# delete artifacts on-disk
|
||||
/bin/rm -rf /Applications/Santa.app
|
||||
/bin/rm -rf /Library/Extensions/santa-driver.kext
|
||||
/bin/rm -f /Library/LaunchAgents/com.google.santagui.plist
|
||||
/bin/rm -f /Library/LaunchAgents/com.google.santa.plist
|
||||
/bin/rm -f /Library/LaunchDaemons/com.google.santad.plist
|
||||
/bin/rm -f /Library/LaunchDaemons/com.google.santa.bundleservice.plist
|
||||
/bin/rm -f /private/etc/asl/com.google.santa.asl.conf
|
||||
/bin/rm -f /private/etc/newsyslog.d/com.google.santa.newsyslog.conf
|
||||
/bin/rm -f /usr/local/bin/santactl # just a symlink
|
||||
|
||||
#uncomment to remove the config file and all databases, log files
|
||||
#/bin/rm -rf /var/db/santa
|
||||
#/bin/rm -f /var/log/santa*
|
||||
exit 0
|
||||
4
Fuzzing/libFuzzer/.gitignore
vendored
Normal file
4
Fuzzing/libFuzzer/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
bin
|
||||
llvm-*.src
|
||||
llvm-*.src.tar.xz
|
||||
|
||||
109
Fuzzing/libFuzzer/build.sh
Executable file
109
Fuzzing/libFuzzer/build.sh
Executable file
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
LLVM_VERSION='5.0.1'
|
||||
LLVM_COMPILERRT_TARBALL_NAME="llvm-${LLVM_VERSION}.src.tar.xz"
|
||||
LLVM_COMPILERRT_SRC_FOLDER_NAME=`echo "${LLVM_COMPILERRT_TARBALL_NAME}" | cut -d '.' -f 1-4`
|
||||
LLVM_COMPILERRT_TARBALL_URL="http://releases.llvm.org/${LLVM_VERSION}/${LLVM_COMPILERRT_TARBALL_NAME}"
|
||||
|
||||
LIBFUZZER_FOLDER="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
LOG_FILE=`mktemp`
|
||||
|
||||
main() {
|
||||
echo "libFuzzer build script"
|
||||
|
||||
echo " > Checking dependencies..."
|
||||
checkDependencies || return 1
|
||||
|
||||
echo " > Entering libFuzzer folder..."
|
||||
cd "${LIBFUZZER_FOLDER}" > /dev/null 2>&1
|
||||
if [ $? -ne 0 ] ; then
|
||||
echo "Failed to enter the libFuzzer folder: ${LIBFUZZER_FOLDER}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ ! -f "${LLVM_COMPILERRT_TARBALL_NAME}" ] ; then
|
||||
echo " > Downloading the LLVM tarball..."
|
||||
curl "${LLVM_COMPILERRT_TARBALL_URL}" -o "${LLVM_COMPILERRT_TARBALL_NAME}" > "${LOG_FILE}" 2>&1
|
||||
if [ $? -ne 0 ] ; then
|
||||
dumpLogFile "Failed to download the LLVM tarball"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
echo " > An existing LLVM tarball was found"
|
||||
fi
|
||||
|
||||
if [ -d "${LLVM_COMPILERRT_SRC_FOLDER_NAME}" ] ; then
|
||||
echo " > Deleting existing LLVM folder..."
|
||||
rm -rf "${LLVM_COMPILERRT_SRC_FOLDER_NAME}" > "${LOG_FILE}" 2>&1
|
||||
if [ $? -ne 0 ] ; then
|
||||
dumpLogFile "Failed to delete the existing source folder"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo " > Extracting the LLVM tarball..."
|
||||
tar xf "${LLVM_COMPILERRT_TARBALL_NAME}" > "${LOG_FILE}" 2>&1
|
||||
if [ $? -ne 0 ] ; then
|
||||
rm "${LLVM_COMPILERRT_TARBALL_NAME}" "${LLVM_COMPILERRT_SRC_FOLDER_NAME}"
|
||||
dumpLogFile "Failed to extract the LLVM tarball"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ -d "bin" ] ; then
|
||||
echo " > Deleting existing bin folder..."
|
||||
rm -rf "bin" > "${LOG_FILE}" 2>&1
|
||||
if [ $? -ne 0 ] ; then
|
||||
dumpLogFile "Failed to delete the existing bin folder"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
mkdir "bin" > "${LOG_FILE}" 2>&1
|
||||
if [ $? -ne 0 ] ; then
|
||||
dumpLogFile "Failed to create the bin folder"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo " > Building libFuzzer..."
|
||||
( cd "bin" && "../${LLVM_COMPILERRT_SRC_FOLDER_NAME}/lib/Fuzzer/build.sh" ) > "${LOG_FILE}" 2>&1
|
||||
if [ $? -ne 0 ] ; then
|
||||
dumpLogFile "Failed to build the library"
|
||||
return 1
|
||||
fi
|
||||
|
||||
printf "\nFinished building libFuzzer\n"
|
||||
rm "${LOG_FILE}"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
checkDependencies() {
|
||||
executable_list=( "clang++" "curl" "tar" )
|
||||
|
||||
for executable in "${executable_list[@]}" ; do
|
||||
which "${executable}" > /dev/null 2>&1
|
||||
if [ $? -ne 0 ] ; then
|
||||
echo "The following program was not found: ${executable}"
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
dumpLogFile() {
|
||||
if [ $# -eq 1 ] ; then
|
||||
local message="$1"
|
||||
else
|
||||
local message="An error has occurred"
|
||||
fi
|
||||
|
||||
printf "${message}\n"
|
||||
printf "Log file follows\n===\n"
|
||||
cat "${LOG_FILE}"
|
||||
printf "\n===\n"
|
||||
rm "${LOG_FILE}"
|
||||
}
|
||||
|
||||
main $@
|
||||
exit $?
|
||||
3
Fuzzing/santacache/.gitignore
vendored
Normal file
3
Fuzzing/santacache/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
santacache.dSYM
|
||||
santacache
|
||||
|
||||
BIN
Fuzzing/santacache/santacache_fuzzer_seed_corpus/example01
Executable file
BIN
Fuzzing/santacache/santacache_fuzzer_seed_corpus/example01
Executable file
Binary file not shown.
41
Fuzzing/santacache/src/main.cpp
Normal file
41
Fuzzing/santacache/src/main.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
/// Copyright 2018 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include <SantaCache.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
|
||||
static SantaCache<uint64_t, uint64_t> decision_cache(5000, 2);
|
||||
|
||||
std::uint64_t fields[2] = {};
|
||||
|
||||
if (size > 16) {
|
||||
std::cout << "Invalid size! Start with -max_len=16\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::memcpy(fields, data, size);
|
||||
|
||||
decision_cache.set(fields[0], fields[1]);
|
||||
auto returned_value = decision_cache.get(fields[0]);
|
||||
|
||||
if (returned_value != fields[1]) {
|
||||
std::cout << fields[0] << ", " << fields[1] << " -> " << returned_value << "\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
16
Fuzzing/santactl/santactl_fuzzer_seed_corpus/example01
Normal file
16
Fuzzing/santactl/santactl_fuzzer_seed_corpus/example01
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"rules": [
|
||||
{
|
||||
"rule_type": "BINARY",
|
||||
"policy": "BLACKLIST",
|
||||
"sha256": "2dc104631939b4bdf5d6bccab76e166e37fe5e1605340cf68dab919df58b8eda",
|
||||
"custom_msg": "blacklist firefox"
|
||||
},
|
||||
{
|
||||
"rule_type": "CERTIFICATE",
|
||||
"policy": "BLACKLIST",
|
||||
"sha256": "e7726cf87cba9e25139465df5bd1557c8a8feed5c7dd338342d8da0959b63c8d",
|
||||
"custom_msg": "blacklist dash app certificate"
|
||||
}
|
||||
]
|
||||
}
|
||||
62
Fuzzing/santactl/src/main.mm
Normal file
62
Fuzzing/santactl/src/main.mm
Normal file
@@ -0,0 +1,62 @@
|
||||
/// Copyright 2018 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include <SNTCommandSyncRuleDownload.h>
|
||||
#include <SNTCommandSyncState.h>
|
||||
#include <SNTCommandSyncConstants.h>
|
||||
#include <SNTRule.h>
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
|
||||
NSData *buffer = [NSData dataWithBytes:static_cast<const void *>(data) length:size];
|
||||
if (!buffer) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
NSError *error;
|
||||
NSDictionary *response = [NSJSONSerialization JSONObjectWithData:buffer options:0 error:&error];
|
||||
if (!response) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (![response isKindOfClass:[NSDictionary class]]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (![response objectForKey:kRules]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
SNTCommandSyncState *state = [[SNTCommandSyncState alloc] init];
|
||||
if (!state) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
SNTCommandSyncRuleDownload *obj = [[SNTCommandSyncRuleDownload alloc] initWithState:state];
|
||||
if (!obj) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (NSDictionary *ruleDict in response[kRules]) {
|
||||
SNTRule *rule = [obj ruleFromDictionary:ruleDict];
|
||||
if (rule) {
|
||||
std::cerr << "Rule: " << [[rule description] UTF8String] << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
BIN
Fuzzing/santad/santad_checkCacheForVnodeID_fuzzer_seed_corpus/example01
Executable file
BIN
Fuzzing/santad/santad_checkCacheForVnodeID_fuzzer_seed_corpus/example01
Executable file
Binary file not shown.
@@ -0,0 +1 @@
|
||||
К'.p▒└G╗М┐║ЙSЮ╝и▌РУерЭxt1iАЫШ9ы*H╩4R"═©$-├Уww╙+Р╝╘[┼иу╧oС┬ОwRpЗя≤х°е
|
||||
BIN
Fuzzing/santad/santad_databaseRuleAddRules_fuzzer_seed_corpus/example01
Executable file
BIN
Fuzzing/santad/santad_databaseRuleAddRules_fuzzer_seed_corpus/example01
Executable file
Binary file not shown.
55
Fuzzing/santad/src/checkCacheForVnodeID.mm
Normal file
55
Fuzzing/santad/src/checkCacheForVnodeID.mm
Normal file
@@ -0,0 +1,55 @@
|
||||
/// Copyright 2018 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
#import "SNTCommandController.h"
|
||||
#import "SNTRule.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
|
||||
if (size > 16) {
|
||||
std::cerr << "Invalid buffer size of " << size
|
||||
<< " (should be <= 16)" << std::endl;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
santa_vnode_id_t vnodeID = {};
|
||||
std::memcpy(&vnodeID, data, size);
|
||||
|
||||
MOLXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
|
||||
daemonConn.invalidationHandler = ^{
|
||||
printf("An error occurred communicating with the daemon, is it running?\n");
|
||||
exit(1);
|
||||
};
|
||||
|
||||
[daemonConn resume];
|
||||
|
||||
[[daemonConn remoteObjectProxy] checkCacheForVnodeID:vnodeID
|
||||
withReply:^(santa_action_t action) {
|
||||
if (action == ACTION_RESPOND_ALLOW) {
|
||||
std::cerr << "File exists in [whitelist] kernel cache" << std::endl;;
|
||||
} else if (action == ACTION_RESPOND_DENY) {
|
||||
std::cerr << "File exists in [blacklist] kernel cache" << std::endl;;
|
||||
} else if (action == ACTION_UNSET) {
|
||||
std::cerr << "File does not exist in cache" << std::endl;;
|
||||
}
|
||||
}];
|
||||
|
||||
return 0;
|
||||
}
|
||||
51
Fuzzing/santad/src/databaseRemoveEventsWithIDs.mm
Normal file
51
Fuzzing/santad/src/databaseRemoveEventsWithIDs.mm
Normal file
@@ -0,0 +1,51 @@
|
||||
/// Copyright 2018 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
#import "SNTCommandController.h"
|
||||
#import "SNTRule.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
|
||||
auto *eventId = reinterpret_cast<const std::uint64_t *>(data);
|
||||
std::size_t eventIdCount = size / sizeof(std::uint64_t);
|
||||
if (eventIdCount == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
MOLXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
|
||||
daemonConn.invalidationHandler = ^{
|
||||
printf("An error occurred communicating with the daemon, is it running?\n");
|
||||
exit(1);
|
||||
};
|
||||
|
||||
[daemonConn resume];
|
||||
|
||||
NSMutableSet *eventIds = [NSMutableSet setWithCapacity:eventIdCount];
|
||||
for (std::size_t i = 0; i < eventIdCount; i++) {
|
||||
auto id = [NSNumber numberWithInteger:eventId[i]];
|
||||
[eventIds addObject:id];
|
||||
}
|
||||
|
||||
[[daemonConn remoteObjectProxy] databaseRemoveEventsWithIDs:[eventIds allObjects]];
|
||||
return 0;
|
||||
}
|
||||
73
Fuzzing/santad/src/databaseRuleAddRules.mm
Normal file
73
Fuzzing/santad/src/databaseRuleAddRules.mm
Normal file
@@ -0,0 +1,73 @@
|
||||
/// Copyright 2018 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
#import "SNTCommandController.h"
|
||||
#import "SNTRule.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
struct InputData {
|
||||
std::uint32_t cleanSlate;
|
||||
std::uint32_t state;
|
||||
std::uint32_t type;
|
||||
char hash[33];
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
|
||||
if (size > sizeof(InputData)) {
|
||||
std::cerr << "Invalid buffer size of " << size
|
||||
<< " (should be <= " << sizeof(InputData)
|
||||
<< ")" << std::endl;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
InputData input_data = {};
|
||||
std::memcpy(&input_data, data, size);
|
||||
|
||||
SNTRule *newRule = [[SNTRule alloc] init];
|
||||
newRule.state = (SNTRuleState) input_data.state;
|
||||
newRule.type = (SNTRuleType) input_data.type;
|
||||
newRule.shasum = @(input_data.hash);
|
||||
newRule.customMsg = @"";
|
||||
|
||||
MOLXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
|
||||
daemonConn.invalidationHandler = ^{
|
||||
printf("An error occurred communicating with the daemon, is it running?\n");
|
||||
exit(1);
|
||||
};
|
||||
|
||||
[daemonConn resume];
|
||||
[[daemonConn remoteObjectProxy] databaseRuleAddRules:@[newRule]
|
||||
cleanSlate:NO
|
||||
reply:^(NSError *error) {
|
||||
if (!error) {
|
||||
if (newRule.state == SNTRuleStateRemove) {
|
||||
printf("Removed rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
|
||||
} else {
|
||||
printf("Added rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
return 0;
|
||||
}
|
||||
36
Podfile
36
Podfile
@@ -1,24 +1,22 @@
|
||||
platform :osx, "10.9"
|
||||
|
||||
inhibit_all_warnings!
|
||||
def common_pods
|
||||
pod 'MOLXPCConnection'
|
||||
pod 'MOLCodesignChecker'
|
||||
pod 'FMDB'
|
||||
pod 'MOLCertificate'
|
||||
pod 'OCMock'
|
||||
pod 'MOLAuthenticatingURLSession'
|
||||
pod 'MOLFCMClient'
|
||||
end
|
||||
|
||||
target :santad do
|
||||
pod 'FMDB'
|
||||
project './Santa.xcodeproj'
|
||||
|
||||
post_install do |rep|
|
||||
rep.project.targets.each do |target|
|
||||
target.build_configurations.each do |config|
|
||||
if config.name != 'Release' then
|
||||
break
|
||||
end
|
||||
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ''
|
||||
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] <<= "NDEBUG=1"
|
||||
end
|
||||
end
|
||||
project = Xcodeproj::Project.open "./Santa.xcodeproj"
|
||||
project.targets.each do |t|
|
||||
if t.name == "santa-driver"
|
||||
next
|
||||
end
|
||||
target t.name do
|
||||
common_pods
|
||||
end
|
||||
end
|
||||
|
||||
target :LogicTests do
|
||||
pod 'OCMock'
|
||||
pod 'FMDB'
|
||||
end
|
||||
|
||||
49
Podfile.lock
49
Podfile.lock
@@ -1,17 +1,46 @@
|
||||
PODS:
|
||||
- FMDB (2.5):
|
||||
- FMDB/standard (= 2.5)
|
||||
- FMDB/common (2.5)
|
||||
- FMDB/standard (2.5):
|
||||
- FMDB/common
|
||||
- OCMock (3.1.2)
|
||||
- FMDB (2.7.5):
|
||||
- FMDB/standard (= 2.7.5)
|
||||
- FMDB/standard (2.7.5)
|
||||
- MOLAuthenticatingURLSession (2.4):
|
||||
- MOLCertificate (~> 1.8)
|
||||
- MOLCertificate (1.9)
|
||||
- MOLCodesignChecker (1.10):
|
||||
- MOLCertificate (~> 1.8)
|
||||
- MOLFCMClient (1.8):
|
||||
- MOLAuthenticatingURLSession (~> 2.4)
|
||||
- MOLXPCConnection (1.2):
|
||||
- MOLCodesignChecker (~> 1.9)
|
||||
- OCMock (3.5)
|
||||
|
||||
DEPENDENCIES:
|
||||
- FMDB
|
||||
- MOLAuthenticatingURLSession
|
||||
- MOLCertificate
|
||||
- MOLCodesignChecker
|
||||
- MOLFCMClient
|
||||
- MOLXPCConnection
|
||||
- OCMock
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
FMDB: 96e8f1bcc1329e269330f99770ad4285d9003e52
|
||||
OCMock: a10ea9f0a6e921651f96f78b6faee95ebc813b92
|
||||
SPEC REPOS:
|
||||
https://github.com/cocoapods/specs.git:
|
||||
- FMDB
|
||||
- MOLAuthenticatingURLSession
|
||||
- MOLCertificate
|
||||
- MOLCodesignChecker
|
||||
- MOLFCMClient
|
||||
- MOLXPCConnection
|
||||
- OCMock
|
||||
|
||||
COCOAPODS: 0.36.3
|
||||
SPEC CHECKSUMS:
|
||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||
MOLAuthenticatingURLSession: c238aa1c9a7b1077eb39a6f40204bfe76a7d204e
|
||||
MOLCertificate: e9e88a396c57032cab847f51a46e20c730cd752a
|
||||
MOLCodesignChecker: b0d5db9d2f9bd94e0fd093891a5d40e5ad77cbc0
|
||||
MOLFCMClient: 2bfbacd45cc11e1ca3c077e97b80401c4e4a54f1
|
||||
MOLXPCConnection: c27af5cb1c43b18319698b0e568a8ddc2fc1e306
|
||||
OCMock: 4ab4577fc941af31f4a0398f6e7e230cf21fc72a
|
||||
|
||||
PODFILE CHECKSUM: d03767a9915896232523962c98d9ff7294aec2b7
|
||||
|
||||
COCOAPODS: 1.7.5
|
||||
|
||||
251
README.md
251
README.md
@@ -1,140 +1,183 @@
|
||||
Santa [](https://travis-ci.org/google/santa)
|
||||
=====
|
||||
# Santa [![Build Status][build-status-img]][build-status-link] [![Documentation Status][doc-status-img]][doc-status-link]
|
||||
|
||||
Santa is a binary whitelisting/blacklisting system for Mac OS X. It consists of
|
||||
a kernel extension that monitors for executions, a userland daemon that makes
|
||||
execution decisions based on the contents of a SQLite database, a GUI agent that
|
||||
notifies the user in case of a block decision and a command-line utility for
|
||||
managing the system and synchronizing the database with a server.
|
||||
[build-status-img]: https://travis-ci.org/google/santa.png?branch=master
|
||||
[build-status-link]: https://travis-ci.org/google/santa
|
||||
[doc-status-img]: https://readthedocs.org/projects/santa/badge/?version=latest
|
||||
[doc-status-link]: https://santa.readthedocs.io/en/latest/?badge=latest
|
||||
|
||||
Santa is not yet a 1.0. We're writing more tests, fixing bugs, working on TODOs
|
||||
and finishing up a security audit.
|
||||
<p align="center">
|
||||
<img src="./Source/santa/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-128.png" alt="Santa Icon" />
|
||||
</p>
|
||||
|
||||
Santa is named because it keeps track of binaries that are naughty and nice.
|
||||
Santa is a binary whitelisting/blacklisting system for macOS. It consists of a
|
||||
kernel extension (or a system extension on macOS 10.15+) that monitors for executions, a userland daemon that makes
|
||||
execution decisions based on the contents of a SQLite database, a GUI agent
|
||||
that notifies the user in case of a block decision and a command-line utility
|
||||
for managing the system and synchronizing the database with a server.
|
||||
|
||||
It is named Santa because it keeps track of binaries that are naughty or nice.
|
||||
|
||||
Santa is a project of Google's Macintosh Operations Team.
|
||||
|
||||
Features
|
||||
========
|
||||
# Docs
|
||||
|
||||
* Multiple modes: MONITOR and LOCKDOWN. In MONITOR mode all binaries except
|
||||
those marked as blacklisted will be allowed to run, whilst being logged and
|
||||
recorded in the database. In LOCKDOWN mode, only whitelisted binaries are
|
||||
allowed to run.
|
||||
The Santa docs are stored in the
|
||||
[Docs](https://github.com/google/santa/blob/master/docs) directory. A Read the
|
||||
Docs instance is available here: https://santa.readthedocs.io.
|
||||
|
||||
* Codesign listing: Binaries can be whitelisted/blacklisted by their signing
|
||||
certificate, so you can trust/block all binaries by a given publisher. The
|
||||
binary will only be whitelisted by certificate if its signature validates
|
||||
correctly. However, a decision for a binary will override a decision for a
|
||||
certificate; i.e. you can whitelist a certificate while blacklisting a binary
|
||||
signed by that certificate or vice-versa.
|
||||
The docs include deployment options, details on how parts of Santa work and
|
||||
instructions for developing Santa itself.
|
||||
|
||||
# Get Help
|
||||
|
||||
If you have questions or otherwise need help getting started,
|
||||
the [santa-dev](https://groups.google.com/forum/#!forum/santa-dev) group is a
|
||||
great place.
|
||||
|
||||
If you believe you have a bug, feel free to report [an
|
||||
issue](https://github.com/google/santa/isues) and we'll respond as soon as we
|
||||
can.
|
||||
|
||||
|
||||
# Admin-Related Features
|
||||
|
||||
* Multiple modes: In the default MONITOR mode, all binaries except those marked
|
||||
as blacklisted will be allowed to run, whilst being logged and recorded in
|
||||
the events database. In LOCKDOWN mode, only whitelisted binaries are allowed
|
||||
to run.
|
||||
|
||||
* Event logging: When the kext is loaded, all binary launches are logged. When
|
||||
in either mode, all unknown or denied binaries are stored in the database to
|
||||
enable later aggregation.
|
||||
|
||||
* Certificate-based rules, with override levels: Instead of relying on a
|
||||
binary's hash (or 'fingerprint'), executables can be whitelisted/blacklisted
|
||||
by their signing certificate. You can therefore trust/block all binaries by a
|
||||
given publisher that were signed with that cert across version updates. A
|
||||
binary can only be whitelisted by its certificate if its signature validates
|
||||
correctly, but a rule for a binary's fingerprint will override a decision for
|
||||
a certificate; i.e. you can whitelist a certificate while blacklisting a
|
||||
binary signed with that certificate, or vice-versa.
|
||||
|
||||
* Path-based rules (via NSRegularExpression/ICU): This allows a similar feature
|
||||
to that found in Managed Client (the precursor to configuration profiles,
|
||||
which used the same implementation mechanism), Application Launch
|
||||
Restrictions via the mcxalr binary. This implementation carries the added
|
||||
benefit of being configurable via regex, and not relying on LaunchServices.
|
||||
As detailed in the wiki, when evaluating rules this holds the lowest
|
||||
precedence.
|
||||
|
||||
* Failsafe cert rules: You cannot put in a deny rule that would block the
|
||||
certificate used to sign launchd, a.k.a. pid 1, and therefore all components
|
||||
used in macOS. The binaries in every OS update (and in some cases entire new
|
||||
versions) are therefore auto-whitelisted. This does not affect binaries from
|
||||
Apple's App Store, which use various certs that change regularly for common
|
||||
apps. Likewise, you cannot blacklist Santa itself, and Santa uses a distinct
|
||||
separate cert than other Google apps.
|
||||
|
||||
# Intentions and Expectations
|
||||
|
||||
No single system or process will stop *all* attacks, or provide 100% security.
|
||||
Santa is written with the intention of helping protect users from themselves.
|
||||
People often download malware and trust it, giving the malware credentials, or
|
||||
allowing unknown software to exfiltrate more data about your system. As a
|
||||
centrally managed component, Santa can help stop the spread of malware among a
|
||||
large fleet of machines. Independently, Santa can aid in analyzing what is
|
||||
running on your computer.
|
||||
|
||||
Santa is part of a defense-in-depth strategy, and you should continue to
|
||||
protect hosts in whatever other ways you see fit.
|
||||
|
||||
# Security and Performance-Related Features
|
||||
|
||||
* In-kernel caching: whitelisted binaries are cached in the kernel so the
|
||||
processing required to make a request is only done if the binary
|
||||
isn't already cached.
|
||||
processing required to make a request is only done if the binary isn't
|
||||
already cached.
|
||||
|
||||
* Userland components validate each other: each of the userland components (the
|
||||
daemon, the GUI agent and the command-line utility) communicate with each other
|
||||
using XPC and check that their signing certificates are identical before any
|
||||
communication is accepted.
|
||||
|
||||
* Event logging: all executions processed by the userland agent are logged and
|
||||
all unknown or denied binaries are also stored in the database for upload to a
|
||||
server.
|
||||
daemon, the GUI agent and the command-line utility) communicate with each
|
||||
other using XPC and check that their signing certificates are identical
|
||||
before any communication is accepted.
|
||||
|
||||
* Kext uses only KPIs: the kernel extension only uses provided kernel
|
||||
programming interfaces to do its job. This means that the kext code should
|
||||
continue to work across OS versions.
|
||||
programming interfaces to do its job. This means that the kext code should
|
||||
continue to work across OS versions.
|
||||
|
||||
Intentions and Expectations
|
||||
===========================
|
||||
No single system or process will stop *all* attacks, or provide 100% security. Santa is written with the intention of helping protect users from themselves. People often download malware and trust it, giving the malware credentials, or allowing unknown software to exfiltrate more data about your system. As a centrally managed component, Santa can help stop the spread of malware among a larger fleet of machines. Additionally, Santa can aid in analyzing what is running in your fleet.
|
||||
|
||||
Santa is part of a defense-in-depth strategy, and you should continue to protect hosts in whatever other ways you see fit.
|
||||
|
||||
Known Issues
|
||||
============
|
||||
Santa is not yet a 1.0 and we have some known issues to be aware of:
|
||||
# Known Issues
|
||||
|
||||
* Santa only blocks execution (execve and variants), it doesn't protect against
|
||||
dynamic libraries loaded with dlopen, libraries on disk that have been replaced or
|
||||
libraries loaded using DYLD_INSERT_LIBRARIES. We are working on also protecting
|
||||
against these avenues of attack.
|
||||
dynamic libraries loaded with dlopen, libraries on disk that have been
|
||||
replaced, or libraries loaded using `DYLD_INSERT_LIBRARIES`. As of version
|
||||
0.9.1 we *do* address [__PAGEZERO missing issues](b87482e) that were
|
||||
exploited in some versions of macOS. We are working on also protecting
|
||||
against similar avenues of attack.
|
||||
|
||||
* Kext communication security: the kext will only accept a connection from a
|
||||
single client at a time and said client must be running as root. We haven't yet
|
||||
found a good way to ensure the kext only accepts connections from a valid client.
|
||||
single client at a time and said client must be running as root. We haven't
|
||||
yet found a good way to ensure the kext only accepts connections from a valid
|
||||
client.
|
||||
|
||||
* Database protection: the SQLite database is installed with permissions so that
|
||||
only the root user can read/write it. We're considering approaches to secure
|
||||
this further.
|
||||
|
||||
* Sync client: the command-line client includes a command to synchronize with a
|
||||
management server, including the uploading of events that have occurred on the
|
||||
machine and to download new rules. We're still very heavily working on this
|
||||
server (which is AppEngine-based and will be open-sourced in the future), so the
|
||||
sync client code is unfinished. It does show the 'API' that we're expecting to
|
||||
use so if you'd like to write your own management server, feel free to look at
|
||||
how the client currently works (and suggest changes!)
|
||||
* Database protection: the SQLite database is installed with permissions so
|
||||
that only the root user can read/write it. We're considering approaches to
|
||||
secure this further.
|
||||
|
||||
* Scripts: Santa is currently written to ignore any execution that isn't a
|
||||
binary. This is because after weighing the administration cost vs the benefit,
|
||||
we found it wasn't worthwhile. Additionally, a number of applications make use
|
||||
of temporary generated scripts, which we can't possibly whitelist and not doing
|
||||
so would cause problems. We're happy to revisit this (or at least make it an
|
||||
option) if it would be useful to others.
|
||||
binary. This is because after weighing the administration cost vs the
|
||||
benefit, we found it wasn't worthwhile. Additionally, a number of
|
||||
applications make use of temporary generated scripts, which we can't possibly
|
||||
whitelist and not doing so would cause problems. We're happy to revisit this
|
||||
(or at least make it an option) if it would be useful to others.
|
||||
|
||||
* Documentation: There currently isn't any.
|
||||
# Sync Servers
|
||||
|
||||
* Tests: There aren't enough of them.
|
||||
* The `santactl` command-line client includes a flag to synchronize with a
|
||||
management server, which uploads events that have occurred on the machine and
|
||||
downloads new rules. There are several open-source servers you can sync with:
|
||||
|
||||
Building
|
||||
========
|
||||
```sh
|
||||
git clone https://github.com/google/santa
|
||||
cd santa
|
||||
* [Upvote](https://github.com/google/upvote) - An AppEngine-based server
|
||||
that implements social voting to make managing a large fleet easier.
|
||||
* [Moroz](https://github.com/groob/moroz) - A simple golang server that
|
||||
serves hardcoded rules from simple configuration files.
|
||||
* [Zentral](https://github.com/zentralopensource/zentral/wiki) - A
|
||||
centralized service that pulls data from multiple sources and deploy
|
||||
configurations to multiple services.
|
||||
|
||||
# Build a debug build. This will install any necessary CocoaPods, create the
|
||||
# workspace and build, outputting the full log only if an error occurred.
|
||||
# If CocoaPods is not installed, you'll be prompted to install it.
|
||||
#
|
||||
# For other build/install/run options, run rake without any arguments
|
||||
rake build:debug
|
||||
```
|
||||
* Alternatively, `santactl` can configure rules locally (without a sync
|
||||
server).
|
||||
|
||||
Note: the Xcode project is setup to use any installed "Mac Developer" certificate
|
||||
and for security-reasons parts of Santa will not operate properly if not signed.
|
||||
# Screenshots
|
||||
|
||||
Kext Signing
|
||||
============
|
||||
10.9 requires a special Developer ID certificate to sign kernel extensions and
|
||||
if the kext is not signed with one of these special certificates a warning will
|
||||
be shown when loading the kext for the first time. In 10.10 this is a hard error
|
||||
and the kext will not load at all unless the machine is booted with a debug
|
||||
boot-arg.
|
||||
A tool like Santa doesn't really lend itself to screenshots, so here's a video
|
||||
instead.
|
||||
|
||||
<p align="center"> <img src="https://zippy.gfycat.com/MadFatalAmphiuma.gif"
|
||||
alt="Santa Block Video" /> </p>
|
||||
|
||||
# Kext Signing
|
||||
Kernel extensions on macOS 10.9 and later must be signed using an Apple-provided
|
||||
Developer ID certificate with a kernel extension flag. Without it, the only way
|
||||
to load an extension is to enable kext-dev-mode or disable SIP, depending on
|
||||
the OS version.
|
||||
|
||||
There are two possible solutions for this, for distribution purposes:
|
||||
|
||||
1) Use a [pre-built, pre-signed version](https://github.com/google/santa/releases)
|
||||
of the kext that we supply. Each time changes are made to the kext code we will
|
||||
update the pre-built version that you can make use of. This doesn't prevent you
|
||||
from making changes to the non-kext parts of Santa and distributing those.
|
||||
If you make changes to the kext and make a pull request, we can merge them in
|
||||
and distribute a new version of the pre-signed kext.
|
||||
1) Use a [pre-built, pre-signed
|
||||
version](https://github.com/google/santa/releases) of the kext that we supply.
|
||||
Each time changes are made to the kext code we will update the pre-built
|
||||
version that you can make use of. This doesn't prevent you from making changes
|
||||
to the non-kext parts of Santa and distributing those. If you make changes to
|
||||
the kext and make a pull request, we can merge them in and distribute a new
|
||||
version of the pre-signed kext.
|
||||
|
||||
2) Apply for your own [kext signing certificate](https://developer.apple.com/contact/kext/).
|
||||
Apple will only grant this for broad distribution within an organization, they
|
||||
won't issue them just for testing purposes.
|
||||
2) Apply for your own [kext signing
|
||||
certificate](https://developer.apple.com/contact/kext/). Apple will only grant
|
||||
this for broad distribution within an organization, they won't issue them just
|
||||
for testing purposes.
|
||||
|
||||
If you just want to locally test changes to the kext code, you should enable
|
||||
kext-dev mode, instructions for which can be found on the Apple developer site.
|
||||
|
||||
|
||||
Contributing
|
||||
============
|
||||
Patches to this project are very much welcome. Please see the [CONTRIBUTING](https://github.com/google/santa/blob/master/CONTRIBUTING.md)
|
||||
# Contributing
|
||||
Patches to this project are very much welcome. Please see the
|
||||
[CONTRIBUTING](https://github.com/google/santa/blob/master/CONTRIBUTING.md)
|
||||
file.
|
||||
|
||||
Disclaimer
|
||||
==========
|
||||
# Disclaimer
|
||||
This is **not** an official Google product.
|
||||
|
||||
197
Rakefile
197
Rakefile
@@ -1,197 +0,0 @@
|
||||
require 'timeout'
|
||||
|
||||
WORKSPACE = 'Santa.xcworkspace'
|
||||
DEFAULT_SCHEME = 'All'
|
||||
OUTPUT_PATH = 'Build'
|
||||
DIST_PATH = 'Dist'
|
||||
BINARIES = ['Santa.app', 'santa-driver.kext', 'santad', 'santactl']
|
||||
XCPRETTY_DEFAULTS = '-sc'
|
||||
XCODEBUILD_DEFAULTS = "-workspace #{WORKSPACE} -derivedDataPath #{OUTPUT_PATH} -parallelizeTargets"
|
||||
|
||||
task :default do
|
||||
system("rake -sT")
|
||||
end
|
||||
|
||||
def xcodebuild(opts)
|
||||
if system "xcodebuild #{XCODEBUILD_DEFAULTS} #{opts} | " \
|
||||
"xcpretty #{XCPRETTY_DEFAULTS} && " \
|
||||
"exit ${PIPESTATUS[0]}"
|
||||
puts "\e[32mPass\e[0m"
|
||||
else
|
||||
raise "\e[31mFail\e[0m"
|
||||
end
|
||||
end
|
||||
|
||||
task :init do
|
||||
unless File.exists?(WORKSPACE) and File.exists?('Pods')
|
||||
puts "Pods missing, running 'pod install'"
|
||||
system "pod install" or raise "CocoaPods is not installed. Install with 'sudo gem install cocoapods'"
|
||||
end
|
||||
unless system 'xcpretty -v >/dev/null 2>&1'
|
||||
puts "xcpretty is not installed. Install with 'sudo gem install xcpretty'"
|
||||
end
|
||||
end
|
||||
|
||||
task :remove_existing do
|
||||
system 'sudo rm -rf /santa-driver.kext'
|
||||
system 'sudo rm -rf /Applications/Santa.app'
|
||||
system 'sudo rm /usr/libexec/santad'
|
||||
system 'sudo rm /usr/sbin/santactl'
|
||||
end
|
||||
|
||||
desc "Clean"
|
||||
task :clean => :init do
|
||||
puts "Cleaning"
|
||||
xcodebuild("-scheme All clean")
|
||||
FileUtils.rm_rf(OUTPUT_PATH)
|
||||
FileUtils.rm_rf(DIST_PATH)
|
||||
end
|
||||
|
||||
# Build
|
||||
namespace :build do
|
||||
desc "Build: Debug"
|
||||
task :debug do
|
||||
Rake::Task['build:build'].invoke("Debug")
|
||||
end
|
||||
|
||||
desc "Build: Release"
|
||||
task :release do
|
||||
Rake::Task['build:build'].invoke("Release")
|
||||
end
|
||||
|
||||
task :build, [:configuration] => :init do |t, args|
|
||||
config = args[:configuration]
|
||||
puts "Building with configuration: #{config}"
|
||||
xcodebuild("-scheme All -configuration #{config} build")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Install
|
||||
namespace :install do
|
||||
desc "Install: Debug"
|
||||
task :debug do
|
||||
Rake::Task['install:install'].invoke("Debug")
|
||||
end
|
||||
|
||||
desc "Install: Release"
|
||||
task :release do
|
||||
Rake::Task['install:install'].invoke("Release")
|
||||
end
|
||||
|
||||
task :install, [:configuration] do |t, args|
|
||||
config = args[:configuration]
|
||||
system 'sudo cp conf/com.google.santad.plist /Library/LaunchDaemons'
|
||||
system 'sudo cp conf/com.google.santasync.plist /Library/LaunchDaemons'
|
||||
system 'sudo cp conf/com.google.santagui.plist /Library/LaunchAgents'
|
||||
system 'sudo cp conf/com.google.santa.asl.conf /etc/asl'
|
||||
Rake::Task['build:build'].invoke(config)
|
||||
puts "Installing with configuration: #{config}"
|
||||
Rake::Task['remove_existing'].invoke()
|
||||
system "sudo cp -r #{OUTPUT_PATH}/Products/#{config}/santa-driver.kext /"
|
||||
system "sudo cp -r #{OUTPUT_PATH}/Products/#{config}/Santa.app /Applications"
|
||||
system "sudo cp #{OUTPUT_PATH}/Products/#{config}/santad /usr/libexec"
|
||||
system "sudo cp #{OUTPUT_PATH}/Products/#{config}/santactl /usr/sbin"
|
||||
end
|
||||
end
|
||||
|
||||
# Dist
|
||||
task :dist do
|
||||
desc "Create distribution folder"
|
||||
|
||||
Rake::Task['build:build'].invoke("Release")
|
||||
|
||||
FileUtils.rm_rf(DIST_PATH)
|
||||
|
||||
FileUtils.mkdir_p("#{DIST_PATH}/binaries")
|
||||
FileUtils.mkdir_p("#{DIST_PATH}/conf")
|
||||
FileUtils.mkdir_p("#{DIST_PATH}/dsym")
|
||||
|
||||
BINARIES.each do |x|
|
||||
FileUtils.cp_r("#{OUTPUT_PATH}/Products/Release/#{x}", "#{DIST_PATH}/binaries")
|
||||
FileUtils.cp_r("#{OUTPUT_PATH}/Products/Release/#{x}.dSYM", "#{DIST_PATH}/dsym")
|
||||
end
|
||||
|
||||
Dir.glob("Conf/*") {|x| File.directory?(x) or FileUtils.cp(x, "#{DIST_PATH}/conf")}
|
||||
|
||||
puts "Distribution folder created"
|
||||
end
|
||||
|
||||
# Tests
|
||||
namespace :tests do
|
||||
desc "Tests: Logic"
|
||||
task :logic => [:init] do
|
||||
puts "Running logic tests"
|
||||
xcodebuild("-scheme LogicTests test")
|
||||
end
|
||||
|
||||
desc "Tests: Kernel"
|
||||
task :kernel do
|
||||
Rake::Task['unload'].invoke()
|
||||
Rake::Task['install:debug'].invoke()
|
||||
Rake::Task['load_kext'].invoke
|
||||
timeout = 30
|
||||
puts "Running kernel tests with a #{timeout} second timeout"
|
||||
begin
|
||||
Timeout::timeout(timeout) {
|
||||
system "sudo #{OUTPUT_PATH}/Products/Debug/KernelTests"
|
||||
}
|
||||
rescue Timeout::Error
|
||||
puts "ERROR: tests ran for longer than #{timeout} seconds and were killed."
|
||||
end
|
||||
Rake::Task['unload_kext'].execute
|
||||
end
|
||||
end
|
||||
|
||||
# Load/Unload
|
||||
task :unload_daemon do
|
||||
puts "Unloading daemon"
|
||||
system "sudo launchctl unload /Library/LaunchDaemons/com.google.santad.plist 2>/dev/null"
|
||||
end
|
||||
|
||||
task :unload_kext do
|
||||
puts "Unloading kernel extension"
|
||||
system "sudo kextunload -b com.google.santa-driver 2>/dev/null"
|
||||
end
|
||||
|
||||
task :unload_gui do
|
||||
puts "Unloading GUI agent"
|
||||
system "sudo killall Santa 2>/dev/null"
|
||||
end
|
||||
|
||||
desc "Unload"
|
||||
task :unload => [:unload_daemon, :unload_kext, :unload_gui]
|
||||
|
||||
task :load_daemon do
|
||||
puts "Loading daemon"
|
||||
system "sudo launchctl load /Library/LaunchDaemons/com.google.santad.plist"
|
||||
end
|
||||
|
||||
task :load_kext do
|
||||
puts "Loading kernel extension"
|
||||
system "sudo kextload /santa-driver.kext"
|
||||
end
|
||||
|
||||
task :load_gui do
|
||||
puts "Loading GUI agent"
|
||||
system "open /Applications/Santa.app"
|
||||
end
|
||||
|
||||
desc "Load"
|
||||
task :load => [:load_kext, :load_daemon, :load_gui]
|
||||
|
||||
namespace :reload do
|
||||
desc "Reload: Debug"
|
||||
task :debug do
|
||||
Rake::Task['unload'].invoke()
|
||||
Rake::Task['install:debug'].invoke()
|
||||
Rake::Task['load'].invoke()
|
||||
end
|
||||
|
||||
desc "Reload: Release"
|
||||
task :release do
|
||||
Rake::Task['unload'].invoke()
|
||||
Rake::Task['install:release'].invoke()
|
||||
Rake::Task['load'].invoke()
|
||||
end
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,78 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0620"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D91BCDC174E8AE600131A7D"
|
||||
BuildableName = "All"
|
||||
BlueprintName = "All"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D260DAB18B68E12002A0B55"
|
||||
BuildableName = "LogicTests.xctest"
|
||||
BlueprintName = "LogicTests"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D91BCDC174E8AE600131A7D"
|
||||
BuildableName = "All"
|
||||
BlueprintName = "All"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,88 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0620"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D0016A1192BCD3C005E7FCD"
|
||||
BuildableName = "KernelTests"
|
||||
BlueprintName = "KernelTests"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D0016A1192BCD3C005E7FCD"
|
||||
BuildableName = "KernelTests"
|
||||
BlueprintName = "KernelTests"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D0016A1192BCD3C005E7FCD"
|
||||
BuildableName = "KernelTests"
|
||||
BlueprintName = "KernelTests"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D0016A1192BCD3C005E7FCD"
|
||||
BuildableName = "KernelTests"
|
||||
BlueprintName = "KernelTests"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,96 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0620"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "NO"
|
||||
buildForArchiving = "NO"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D260DAB18B68E12002A0B55"
|
||||
BuildableName = "LogicTests.xctest"
|
||||
BlueprintName = "LogicTests"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D260DAB18B68E12002A0B55"
|
||||
BuildableName = "LogicTests.xctest"
|
||||
BlueprintName = "LogicTests"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D260DAB18B68E12002A0B55"
|
||||
BuildableName = "LogicTests.xctest"
|
||||
BlueprintName = "LogicTests"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D260DAB18B68E12002A0B55"
|
||||
BuildableName = "LogicTests.xctest"
|
||||
BlueprintName = "LogicTests"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D260DAB18B68E12002A0B55"
|
||||
BuildableName = "LogicTests.xctest"
|
||||
BlueprintName = "LogicTests"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0620"
|
||||
LastUpgradeVersion = "1100"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -14,7 +14,7 @@
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D385DB5180DE4A900418BC6"
|
||||
BlueprintIdentifier = "C779C2DD22F0E95000EE2541"
|
||||
BuildableName = "Santa.app"
|
||||
BlueprintName = "Santa"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
@@ -23,55 +23,45 @@
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D385DB5180DE4A900418BC6"
|
||||
BuildableName = "Santa.app"
|
||||
BlueprintName = "Santa"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D385DB5180DE4A900418BC6"
|
||||
BlueprintIdentifier = "C779C2DD22F0E95000EE2541"
|
||||
BuildableName = "Santa.app"
|
||||
BlueprintName = "Santa"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D385DB5180DE4A900418BC6"
|
||||
BlueprintIdentifier = "C779C2DD22F0E95000EE2541"
|
||||
BuildableName = "Santa.app"
|
||||
BlueprintName = "Santa"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0620"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D91BCB3174E8A7E00131A7D"
|
||||
BuildableName = "santa-driver.kext"
|
||||
BlueprintName = "santa-driver"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D91BCB3174E8A7E00131A7D"
|
||||
BuildableName = "santa-driver.kext"
|
||||
BlueprintName = "santa-driver"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,88 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0620"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D35BD9D18FD71CE00921A21"
|
||||
BuildableName = "santactl"
|
||||
BlueprintName = "santactl"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D35BD9D18FD71CE00921A21"
|
||||
BuildableName = "santactl"
|
||||
BlueprintName = "santactl"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D35BD9D18FD71CE00921A21"
|
||||
BuildableName = "santactl"
|
||||
BlueprintName = "santactl"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D35BD9D18FD71CE00921A21"
|
||||
BuildableName = "santactl"
|
||||
BlueprintName = "santactl"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0620"
|
||||
LastUpgradeVersion = "1100"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -14,67 +14,57 @@
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D9A7F3C1759330400035EB5"
|
||||
BuildableName = "santad"
|
||||
BlueprintName = "santad"
|
||||
BlueprintIdentifier = "C779C4E522F0F51400EE2541"
|
||||
BuildableName = "com.google.santa.daemon"
|
||||
BlueprintName = "com.google.santa.daemon"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D9A7F3C1759330400035EB5"
|
||||
BuildableName = "santad"
|
||||
BlueprintName = "santad"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
debugAsWhichUser = "root"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D9A7F3C1759330400035EB5"
|
||||
BuildableName = "santad"
|
||||
BlueprintName = "santad"
|
||||
BlueprintIdentifier = "C779C4E522F0F51400EE2541"
|
||||
BuildableName = "com.google.santa.daemon"
|
||||
BlueprintName = "com.google.santa.daemon"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D9A7F3C1759330400035EB5"
|
||||
BuildableName = "santad"
|
||||
BlueprintName = "santad"
|
||||
BlueprintIdentifier = "C779C4E522F0F51400EE2541"
|
||||
BuildableName = "com.google.santa.daemon"
|
||||
BlueprintName = "com.google.santa.daemon"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
|
||||
11
Santa.xcworkspace/contents.xcworkspacedata
generated
11
Santa.xcworkspace/contents.xcworkspacedata
generated
@@ -1 +1,10 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?><Workspace version='1.0'><FileRef location='group:Santa.xcodeproj'/><FileRef location='group:Pods/Pods.xcodeproj'/></Workspace>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Santa.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 9.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 36 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.3 KiB |
@@ -1,289 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6254" systemVersion="14D136" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6254"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="SNTMessageWindowController">
|
||||
<connections>
|
||||
<outlet property="openEventButton" destination="7ua-5a-uSd" id="9s4-ZA-Vlo"/>
|
||||
<outlet property="window" destination="9Bq-yh-54f" id="Uhs-WF-TV9"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="none" id="9Bq-yh-54f" customClass="SNTMessageWindow">
|
||||
<windowStyleMask key="styleMask" utility="YES"/>
|
||||
<rect key="contentRect" x="167" y="107" width="497" height="356"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
|
||||
<view key="contentView" id="Iwq-Lx-rLv">
|
||||
<rect key="frame" x="0.0" y="0.0" width="497" height="356"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="t8c-Fx-e5h">
|
||||
<rect key="frame" x="207" y="286" width="83" height="40"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Santa" id="7YA-iB-Zma">
|
||||
<font key="font" size="34" name="HelveticaNeue-UltraLight"/>
|
||||
<color key="textColor" red="0.18696189413265307" green="0.18696189413265307" blue="0.18696189413265307" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cD5-Su-lXR">
|
||||
<rect key="frame" x="22" y="239" width="454" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="450" id="XgJ-EV-tBa"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="center" title="A message to the user goes here..." allowsEditingTextAttributes="YES" id="5tH-bG-UJA">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="self.attributedCustomMessage" id="376-sj-4Q1"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pc8-G9-4pJ">
|
||||
<rect key="frame" x="165" y="192" width="294" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="290" id="xVR-j3-dLw"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Binary Path" id="E7T-9h-ofr">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" white="0.0" alpha="0.5" colorSpace="deviceWhite"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="self.event.filePath" id="qfp-sR-Nmu"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="PXc-xv-A28">
|
||||
<rect key="frame" x="165" y="142" width="294" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="290" id="4hh-R2-86s"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" lineBreakMode="charWrapping" selectable="YES" sendsActionOnEndEditing="YES" title="Part of SHA-256" id="X4W-9e-eIu">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" white="0.0" alpha="0.5" colorSpace="deviceWhite"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="self.shortenedHash" id="xgu-71-9ZT"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="C3G-wL-u7w">
|
||||
<rect key="frame" x="165" y="167" width="294" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="290" id="Dem-wH-KHm"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" selectable="YES" allowsUndo="NO" sendsActionOnEndEditing="YES" title="Code signing information" placeholderString="" id="ztA-La-XgT">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" white="0.0" alpha="0.5" colorSpace="deviceWhite"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="self.publisherInfo" id="CEI-Cu-7pC">
|
||||
<dictionary key="options">
|
||||
<string key="NSNullPlaceholder">Not code-signed</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="oFj-ol-xpL">
|
||||
<rect key="frame" x="8" y="92" width="120" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="User" id="1ut-uT-hQD">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="eQb-0a-76J">
|
||||
<rect key="frame" x="8" y="117" width="120" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Parent" id="gze-4A-1w5">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lvJ-Rk-UT5">
|
||||
<rect key="frame" x="8" y="167" width="120" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Publisher" id="yL9-yD-JXX">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="d9e-Wv-Y5H">
|
||||
<rect key="frame" x="8" y="192" width="120" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="116" id="Kqd-nX-7df"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Path" id="KgY-X1-ESG">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="KEB-eH-x2Y">
|
||||
<rect key="frame" x="8" y="142" width="120" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Identifier" id="eKN-Ic-5zy">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="h6f-PY-cc0">
|
||||
<rect key="frame" x="165" y="92" width="294" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="290" id="on6-pj-m2k"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Executing User" id="HRT-Be-ePf">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" white="0.0" alpha="0.5" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="self.event.executingUser" id="IcM-Lt-xTT">
|
||||
<dictionary key="options">
|
||||
<string key="NSNullPlaceholder">Unknown</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
</connections>
|
||||
</textField>
|
||||
<box horizontalHuggingPriority="750" title="Line" boxType="custom" borderType="line" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="4Li-ul-zIi">
|
||||
<rect key="frame" x="146" y="92" width="1" height="117"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="1" id="0o1-Jh-epf"/>
|
||||
</constraints>
|
||||
<color key="borderColor" white="0.0" alpha="0.18" colorSpace="calibratedWhite"/>
|
||||
<color key="fillColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
<font key="titleFont" metaFont="system"/>
|
||||
</box>
|
||||
<button toolTip="Show code signing certificate chain" translatesAutoresizingMaskIntoConstraints="NO" id="cJf-k6-OxS" userLabel="Publisher Certs">
|
||||
<rect key="frame" x="40" y="168" width="15" height="15"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="15" id="QTm-Iv-m5p"/>
|
||||
<constraint firstAttribute="height" constant="15" id="YwG-0s-jop"/>
|
||||
</constraints>
|
||||
<buttonCell key="cell" type="bevel" bezelStyle="regularSquare" image="NSInfo" imagePosition="overlaps" alignment="center" refusesFirstResponder="YES" imageScaling="proportionallyDown" inset="2" id="R72-Qy-Xbb">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="showCertInfo:" target="-2" id="dB0-a3-X31"/>
|
||||
<binding destination="-2" name="hidden" keyPath="self.publisherInfo" id="fFR-f3-Oiw">
|
||||
<dictionary key="options">
|
||||
<string key="NSValueTransformerName">NSIsNil</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="BbV-3h-mmL">
|
||||
<rect key="frame" x="256" y="33" width="110" height="25"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="110" id="6Uh-Bd-N64"/>
|
||||
<constraint firstAttribute="height" constant="22" id="GH6-nw-6rD"/>
|
||||
</constraints>
|
||||
<buttonCell key="cell" type="roundTextured" title="Dismiss" bezelStyle="texturedRounded" alignment="center" refusesFirstResponder="YES" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="XR6-Xa-gP4">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
DQ
|
||||
</string>
|
||||
<modifierMask key="keyEquivalentModifierMask" shift="YES"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="closeWindow:" target="-2" id="qQq-gh-8lw"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" horizontalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="7ua-5a-uSd">
|
||||
<rect key="frame" x="132" y="33" width="112" height="25"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" priority="900" constant="112" id="Pec-Pa-4aZ"/>
|
||||
</constraints>
|
||||
<buttonCell key="cell" type="roundTextured" title="Open Event..." bezelStyle="texturedRounded" alignment="center" refusesFirstResponder="YES" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="X1b-TF-1TL">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
DQ
|
||||
</string>
|
||||
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="openEventDetails:" target="-2" id="VhL-ql-rCV"/>
|
||||
</connections>
|
||||
</button>
|
||||
<textField horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="f1p-GL-O3o">
|
||||
<rect key="frame" x="165" y="117" width="294" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="290" id="h3Y-mO-38F"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Parent Name" id="ieo-WK-aDD">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" white="0.0" alpha="0.5" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="self.event.parentName" id="arL-Mc-4xj">
|
||||
<dictionary key="options">
|
||||
<string key="NSNullPlaceholder">Unknown</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
</connections>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="f1p-GL-O3o" firstAttribute="centerY" secondItem="eQb-0a-76J" secondAttribute="centerY" id="2Aq-1E-Ybz"/>
|
||||
<constraint firstItem="h6f-PY-cc0" firstAttribute="top" secondItem="f1p-GL-O3o" secondAttribute="bottom" constant="8" id="496-VQ-Fx5"/>
|
||||
<constraint firstItem="eQb-0a-76J" firstAttribute="leading" secondItem="lvJ-Rk-UT5" secondAttribute="trailing" constant="-116" id="6Q5-Oo-1cI"/>
|
||||
<constraint firstItem="BbV-3h-mmL" firstAttribute="top" secondItem="oFj-ol-xpL" secondAttribute="bottom" constant="35" id="7K6-bY-Rn6"/>
|
||||
<constraint firstItem="C3G-wL-u7w" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="ALv-0v-szi"/>
|
||||
<constraint firstItem="f1p-GL-O3o" firstAttribute="top" secondItem="PXc-xv-A28" secondAttribute="bottom" constant="8" id="E6D-7P-17g"/>
|
||||
<constraint firstItem="cJf-k6-OxS" firstAttribute="centerY" secondItem="C3G-wL-u7w" secondAttribute="centerY" id="FdL-ZZ-Vbe"/>
|
||||
<constraint firstItem="t8c-Fx-e5h" firstAttribute="top" secondItem="Iwq-Lx-rLv" secondAttribute="top" constant="30" id="FuB-GX-0jg"/>
|
||||
<constraint firstItem="oFj-ol-xpL" firstAttribute="bottom" secondItem="4Li-ul-zIi" secondAttribute="bottom" id="G0I-O2-S91"/>
|
||||
<constraint firstItem="lvJ-Rk-UT5" firstAttribute="leading" secondItem="cJf-k6-OxS" secondAttribute="trailing" constant="-45" id="GD2-Ka-deo"/>
|
||||
<constraint firstItem="h6f-PY-cc0" firstAttribute="centerY" secondItem="oFj-ol-xpL" secondAttribute="centerY" id="GXI-pT-FM1"/>
|
||||
<constraint firstItem="oFj-ol-xpL" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="IwX-ja-ZIs"/>
|
||||
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="top" secondItem="4Li-ul-zIi" secondAttribute="top" id="JY4-N1-j8e"/>
|
||||
<constraint firstItem="oFj-ol-xpL" firstAttribute="leading" secondItem="d9e-Wv-Y5H" secondAttribute="leading" priority="999" id="MVr-jY-GDj"/>
|
||||
<constraint firstItem="pc8-G9-4pJ" firstAttribute="top" secondItem="cD5-Su-lXR" secondAttribute="bottom" constant="30" id="Nsl-zf-poH"/>
|
||||
<constraint firstItem="pc8-G9-4pJ" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="SCl-Ky-VmT"/>
|
||||
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="centerY" secondItem="pc8-G9-4pJ" secondAttribute="centerY" id="SLv-F7-w5k"/>
|
||||
<constraint firstItem="7ua-5a-uSd" firstAttribute="top" secondItem="oFj-ol-xpL" secondAttribute="bottom" constant="35" id="Scq-zQ-Sao"/>
|
||||
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="KEB-eH-x2Y" secondAttribute="trailing" constant="20" id="Seb-c0-MUL"/>
|
||||
<constraint firstAttribute="centerX" secondItem="cD5-Su-lXR" secondAttribute="centerX" id="V0a-Py-iEc"/>
|
||||
<constraint firstItem="oFj-ol-xpL" firstAttribute="leading" secondItem="lvJ-Rk-UT5" secondAttribute="leading" priority="999" id="Z6G-l9-G4a"/>
|
||||
<constraint firstItem="oFj-ol-xpL" firstAttribute="top" secondItem="eQb-0a-76J" secondAttribute="bottom" constant="8" id="abm-cM-PN0"/>
|
||||
<constraint firstAttribute="centerX" secondItem="BbV-3h-mmL" secondAttribute="centerX" priority="900" constant="-62.5" id="acs-5J-vQY"/>
|
||||
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="eQb-0a-76J" secondAttribute="trailing" constant="20" id="b0B-3w-grH"/>
|
||||
<constraint firstItem="KEB-eH-x2Y" firstAttribute="leading" secondItem="oFj-ol-xpL" secondAttribute="leading" priority="999" id="b5A-M7-ZsD"/>
|
||||
<constraint firstItem="KEB-eH-x2Y" firstAttribute="centerY" secondItem="PXc-xv-A28" secondAttribute="centerY" id="cHe-pZ-0Oq"/>
|
||||
<constraint firstItem="cD5-Su-lXR" firstAttribute="top" secondItem="t8c-Fx-e5h" secondAttribute="bottom" constant="30" id="dYg-zP-wh2"/>
|
||||
<constraint firstItem="h6f-PY-cc0" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="eSz-lz-Fdh"/>
|
||||
<constraint firstItem="f1p-GL-O3o" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="fGd-YS-phP"/>
|
||||
<constraint firstAttribute="centerX" secondItem="t8c-Fx-e5h" secondAttribute="centerX" id="h3d-Kc-q88"/>
|
||||
<constraint firstItem="BbV-3h-mmL" firstAttribute="leading" secondItem="7ua-5a-uSd" secondAttribute="trailing" constant="12" id="ioO-NJ-Jqo"/>
|
||||
<constraint firstItem="C3G-wL-u7w" firstAttribute="centerY" secondItem="lvJ-Rk-UT5" secondAttribute="centerY" id="jfs-YI-7Ae"/>
|
||||
<constraint firstItem="lvJ-Rk-UT5" firstAttribute="trailing" secondItem="KEB-eH-x2Y" secondAttribute="trailing" id="jlD-Lo-abc"/>
|
||||
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="oFj-ol-xpL" secondAttribute="trailing" constant="20" id="kOG-Cj-hFG"/>
|
||||
<constraint firstItem="oFj-ol-xpL" firstAttribute="trailing" secondItem="lvJ-Rk-UT5" secondAttribute="trailing" id="lse-kg-lA2"/>
|
||||
<constraint firstItem="eQb-0a-76J" firstAttribute="top" secondItem="KEB-eH-x2Y" secondAttribute="bottom" constant="8" id="m2z-1O-ifB"/>
|
||||
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="trailing" secondItem="KEB-eH-x2Y" secondAttribute="trailing" id="pdq-a6-Y73"/>
|
||||
<constraint firstAttribute="centerX" secondItem="7ua-5a-uSd" secondAttribute="centerX" constant="61" id="phL-j9-rPq"/>
|
||||
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="lvJ-Rk-UT5" secondAttribute="trailing" constant="20" id="qKi-KT-jzJ"/>
|
||||
<constraint firstItem="C3G-wL-u7w" firstAttribute="bottom" secondItem="PXc-xv-A28" secondAttribute="top" constant="-8" id="snd-8T-LjC"/>
|
||||
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="d9e-Wv-Y5H" secondAttribute="trailing" constant="20" id="stz-Vm-Kxo"/>
|
||||
<constraint firstItem="PXc-xv-A28" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="tAa-1s-xVZ"/>
|
||||
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="width" secondItem="eQb-0a-76J" secondAttribute="width" id="u4p-1B-x5B"/>
|
||||
<constraint firstAttribute="bottom" secondItem="BbV-3h-mmL" secondAttribute="bottom" constant="35" id="ukF-FH-DE8"/>
|
||||
<constraint firstItem="pc8-G9-4pJ" firstAttribute="bottom" secondItem="C3G-wL-u7w" secondAttribute="top" constant="-8" id="zst-nc-VqA"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<point key="canvasLocation" x="112.5" y="308"/>
|
||||
</window>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="NSInfo" width="32" height="32"/>
|
||||
</resources>
|
||||
</document>
|
||||
@@ -1,3 +0,0 @@
|
||||
#ifdef __OBJC__
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#endif
|
||||
@@ -1,108 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTAppDelegate.h"
|
||||
|
||||
#import "SNTAboutWindowController.h"
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTFileWatcher.h"
|
||||
#import "SNTNotificationManager.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
|
||||
@interface SNTAppDelegate ()
|
||||
@property SNTAboutWindowController *aboutWindowController;
|
||||
@property SNTFileWatcher *configFileWatcher;
|
||||
@property SNTNotificationManager *notificationManager;
|
||||
@property SNTXPCConnection *listener;
|
||||
@end
|
||||
|
||||
@implementation SNTAppDelegate
|
||||
|
||||
#pragma mark App Delegate methods
|
||||
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||
[self setupMenu];
|
||||
|
||||
self.configFileWatcher = [[SNTFileWatcher alloc] initWithFilePath:kDefaultConfigFilePath
|
||||
handler:^{
|
||||
[[SNTConfigurator configurator] reloadConfigData];
|
||||
}];
|
||||
|
||||
self.notificationManager = [[SNTNotificationManager alloc] init];
|
||||
|
||||
NSNotificationCenter *workspaceNotifications = [[NSWorkspace sharedWorkspace] notificationCenter];
|
||||
[workspaceNotifications addObserver:self
|
||||
selector:@selector(killConnection)
|
||||
name:NSWorkspaceSessionDidResignActiveNotification
|
||||
object:nil];
|
||||
[workspaceNotifications addObserver:self
|
||||
selector:@selector(createConnection)
|
||||
name:NSWorkspaceSessionDidBecomeActiveNotification
|
||||
object:nil];
|
||||
|
||||
[self createConnection];
|
||||
}
|
||||
|
||||
- (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag {
|
||||
self.aboutWindowController = [[SNTAboutWindowController alloc] init];
|
||||
[self.aboutWindowController showWindow:self];
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark Connection handling
|
||||
|
||||
- (void)createConnection {
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
|
||||
self.listener = [[SNTXPCConnection alloc] initClientWithName:[SNTXPCNotifierInterface serviceId]
|
||||
options:NSXPCConnectionPrivileged];
|
||||
self.listener.exportedInterface = [SNTXPCNotifierInterface notifierInterface];
|
||||
self.listener.exportedObject = self.notificationManager;
|
||||
self.listener.rejectedHandler = ^{
|
||||
[weakSelf performSelectorInBackground:@selector(attemptReconnection)
|
||||
withObject:nil];
|
||||
};
|
||||
self.listener.invalidationHandler = self.listener.rejectedHandler;
|
||||
[self.listener resume];
|
||||
}
|
||||
|
||||
- (void)killConnection {
|
||||
self.listener.invalidationHandler = nil;
|
||||
[self.listener invalidate];
|
||||
self.listener = nil;
|
||||
}
|
||||
|
||||
- (void)attemptReconnection {
|
||||
// TODO(rah): Make this smarter.
|
||||
sleep(10);
|
||||
[self performSelectorOnMainThread:@selector(createConnection) withObject:nil waitUntilDone:NO];
|
||||
}
|
||||
|
||||
#pragma mark Menu Management
|
||||
|
||||
- (void)setupMenu {
|
||||
// Whilst the user will never see the menu, having one with the Copy and Select All options
|
||||
// allows the shortcuts for these items to work, which is useful for being able to copy
|
||||
// information from notifications. The mainMenu must have a nested menu for this to work properly.
|
||||
NSMenu *mainMenu = [[NSMenu alloc] init];
|
||||
NSMenu *editMenu = [[NSMenu alloc] init];
|
||||
[editMenu addItemWithTitle:@"Copy" action:@selector(copy:) keyEquivalent:@"c"];
|
||||
[editMenu addItemWithTitle:@"Select All" action:@selector(selectAll:) keyEquivalent:@"a"];
|
||||
NSMenuItem *editMenuItem = [[NSMenuItem alloc] init];
|
||||
[editMenuItem setSubmenu:editMenu];
|
||||
[mainMenu addItem:editMenuItem];
|
||||
[NSApp setMainMenu:mainMenu];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,148 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTMessageWindowController.h"
|
||||
|
||||
#import <SecurityInterface/SFCertificatePanel.h>
|
||||
|
||||
#import "SNTCertificate.h"
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTFileInfo.h"
|
||||
#import "SNTMessageWindow.h"
|
||||
#import "SNTStoredEvent.h"
|
||||
|
||||
@implementation SNTMessageWindowController
|
||||
|
||||
- (instancetype)initWithEvent:(SNTStoredEvent *)event andMessage:(NSString *)message {
|
||||
self = [super initWithWindowNibName:@"MessageWindow"];
|
||||
if (self) {
|
||||
_event = event;
|
||||
_customMessage = (message != (NSString *)[NSNull null] ? message : nil);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)loadWindow {
|
||||
[super loadWindow];
|
||||
[self.window setLevel:NSPopUpMenuWindowLevel];
|
||||
[self.window setMovableByWindowBackground:YES];
|
||||
|
||||
if (![[SNTConfigurator configurator] eventDetailURL]) {
|
||||
[self.openEventButton removeFromSuperview];
|
||||
} else {
|
||||
NSString *eventDetailText = [[SNTConfigurator configurator] eventDetailText];
|
||||
if (eventDetailText) {
|
||||
[self.openEventButton setTitle:eventDetailText];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)showWindow:(id)sender {
|
||||
[(SNTMessageWindow *)self.window fadeIn:sender];
|
||||
}
|
||||
|
||||
- (IBAction)closeWindow:(id)sender {
|
||||
[(SNTMessageWindow *)self.window fadeOut:sender];
|
||||
}
|
||||
|
||||
- (void)windowWillClose:(NSNotification *)notification {
|
||||
if (self.delegate) [self.delegate windowDidClose];
|
||||
}
|
||||
|
||||
- (IBAction)showCertInfo:(id)sender {
|
||||
// SFCertificatePanel expects an NSArray of SecCertificateRef's
|
||||
NSMutableArray *certArray = [NSMutableArray arrayWithCapacity:[self.event.signingChain count]];
|
||||
for (SNTCertificate *cert in self.event.signingChain) {
|
||||
[certArray addObject:(id)cert.certRef];
|
||||
}
|
||||
|
||||
[[[SFCertificatePanel alloc] init] beginSheetForWindow:self.window
|
||||
modalDelegate:nil
|
||||
didEndSelector:nil
|
||||
contextInfo:nil
|
||||
certificates:certArray
|
||||
showGroup:YES];
|
||||
}
|
||||
|
||||
- (IBAction)openEventDetails:(id)sender {
|
||||
SNTConfigurator *config = [SNTConfigurator configurator];
|
||||
|
||||
NSString *formatStr = config.eventDetailURL;
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%file_sha%"
|
||||
withString:self.event.fileSHA256];
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%username%"
|
||||
withString:self.event.executingUser];
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%machine_id%"
|
||||
withString:config.machineID];
|
||||
|
||||
[self closeWindow:sender];
|
||||
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:formatStr]];
|
||||
}
|
||||
|
||||
#pragma mark Generated properties
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
|
||||
if (![key isEqualToString:@"event"]) {
|
||||
return [NSSet setWithObject:@"event"];
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)shortenedHash {
|
||||
return [self.event.fileSHA256 substringWithRange:NSMakeRange(0, 10)];
|
||||
}
|
||||
|
||||
- (NSString *)publisherInfo {
|
||||
SNTCertificate *leafCert = [self.event.signingChain firstObject];
|
||||
|
||||
if (leafCert.commonName && leafCert.orgName) {
|
||||
return [NSString stringWithFormat:@"%@ - %@", leafCert.orgName, leafCert.commonName];
|
||||
} else if (leafCert.commonName) {
|
||||
return leafCert.commonName;
|
||||
} else if (leafCert.orgName) {
|
||||
return leafCert.orgName;
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSAttributedString *)attributedCustomMessage {
|
||||
NSString *htmlHeader = @"<html><head><style>"
|
||||
@"body {"
|
||||
@" font-family: 'Lucida Grande', 'Helvetica', sans-serif;"
|
||||
@" font-size: 13px;"
|
||||
@" color: #AAA;"
|
||||
@" text-align: center;"
|
||||
@"}"
|
||||
@"</style></head><body>";
|
||||
NSString *htmlFooter = @"</body></html>";
|
||||
|
||||
NSString *message;
|
||||
if ([self.customMessage length] > 0) {
|
||||
message = self.customMessage;
|
||||
} else {
|
||||
message = @"The following application has been blocked from executing<br />"
|
||||
@"because its trustworthiness cannot be determined.";
|
||||
}
|
||||
|
||||
NSString *fullHTML = [NSString stringWithFormat:@"%@%@%@", htmlHeader, message, htmlFooter];
|
||||
|
||||
NSData *htmlData = [fullHTML dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSAttributedString *returnStr = [[NSAttributedString alloc] initWithHTML:htmlData
|
||||
documentAttributes:NULL];
|
||||
return returnStr;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,96 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTNotificationManager.h"
|
||||
|
||||
#import "SNTStoredEvent.h"
|
||||
|
||||
@interface SNTNotificationManager ()
|
||||
///
|
||||
/// The currently displayed notification
|
||||
///
|
||||
@property SNTMessageWindowController *currentWindowController;
|
||||
|
||||
///
|
||||
/// The queue of pending notifications
|
||||
///
|
||||
@property(readonly) NSMutableArray *pendingNotifications;
|
||||
@end
|
||||
|
||||
@implementation SNTNotificationManager
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_pendingNotifications = [[NSMutableArray alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)windowDidClose {
|
||||
[self.pendingNotifications removeObject:self.currentWindowController];
|
||||
self.currentWindowController = nil;
|
||||
|
||||
if ([self.pendingNotifications count]) {
|
||||
self.currentWindowController = [self.pendingNotifications firstObject];
|
||||
[self.currentWindowController showWindow:self];
|
||||
} else {
|
||||
[NSApp hide:self];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark SNTNotifierXPC protocol methods
|
||||
|
||||
- (void)postBlockNotification:(SNTStoredEvent *)event withCustomMessage:(NSString *)message {
|
||||
// See if this binary is already in the list of pending notifications.
|
||||
NSPredicate *predicate =
|
||||
[NSPredicate predicateWithFormat:@"event.fileSHA256==%@", event.fileSHA256];
|
||||
if ([[self.pendingNotifications filteredArrayUsingPredicate:predicate] count]) return;
|
||||
|
||||
if (!event) {
|
||||
NSLog(@"Error: Missing event object in message received from daemon!");
|
||||
return;
|
||||
}
|
||||
if (!message) message = (NSString *)[NSNull null];
|
||||
|
||||
// Notifications arrive on a background thread but UI updates must happen on the main thread.
|
||||
// This includes making windows.
|
||||
[self performSelectorOnMainThread:@selector(postBlockNotificationMainThread:)
|
||||
withObject:@{ @"event": event, @"custommsg": message }
|
||||
waitUntilDone:NO];
|
||||
}
|
||||
|
||||
- (void)postBlockNotificationMainThread:(NSDictionary *)dict {
|
||||
SNTStoredEvent *event = dict[@"event"];
|
||||
NSString *msg = dict[@"custommsg"];
|
||||
|
||||
// Create message window
|
||||
SNTMessageWindowController *pendingMsg = [[SNTMessageWindowController alloc] initWithEvent:event
|
||||
andMessage:msg];
|
||||
pendingMsg.delegate = self;
|
||||
[self.pendingNotifications addObject:pendingMsg];
|
||||
|
||||
// If a notification isn't currently being displayed, display the incoming one.
|
||||
if (!self.currentWindowController) {
|
||||
self.currentWindowController = pendingMsg;
|
||||
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
|
||||
// It's quite likely that we're currently on a background thread, and GUI code should always be
|
||||
// on main thread. Open the window on the main thread so any code it runs is also.
|
||||
[pendingMsg showWindow:nil];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
212
Source/common/BUILD
Normal file
212
Source/common/BUILD
Normal file
@@ -0,0 +1,212 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"]) # Apache 2.0
|
||||
|
||||
load("//:helper.bzl", "santa_unit_test")
|
||||
|
||||
objc_library(
|
||||
name = "SNTBlockMessage",
|
||||
srcs = ["SNTBlockMessage.m"],
|
||||
hdrs = ["SNTBlockMessage.h"],
|
||||
deps = [
|
||||
":SNTConfigurator",
|
||||
":SNTStoredEvent",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTBlockMessage_SantaGUI",
|
||||
srcs = ["SNTBlockMessage.m"],
|
||||
hdrs = ["SNTBlockMessage.h"],
|
||||
defines = ["SANTAGUI"],
|
||||
deps = [
|
||||
":SNTConfigurator",
|
||||
":SNTStoredEvent",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTCachedDecision",
|
||||
srcs = ["SNTCachedDecision.m"],
|
||||
hdrs = ["SNTCachedDecision.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTKernelCommon",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "SNTCommonEnums",
|
||||
hdrs = ["SNTCommonEnums.h"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTConfigurator",
|
||||
srcs = ["SNTConfigurator.m"],
|
||||
hdrs = ["SNTConfigurator.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTLogging",
|
||||
":SNTStrengthify",
|
||||
":SNTSystemInfo",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTDropRootPrivs",
|
||||
srcs = ["SNTDropRootPrivs.m"],
|
||||
hdrs = ["SNTDropRootPrivs.h"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTFileInfo",
|
||||
srcs = ["SNTFileInfo.m"],
|
||||
hdrs = ["SNTFileInfo.h"],
|
||||
deps = [
|
||||
"@FMDB",
|
||||
"@MOLCodesignChecker",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "SNTKernelCommon",
|
||||
hdrs = ["SNTKernelCommon.h"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "SNTLoggingKernel",
|
||||
hdrs = ["SNTLogging.h"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTLogging",
|
||||
srcs = ["SNTLogging.m"],
|
||||
hdrs = ["SNTLogging.h"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "SNTPrefixTree",
|
||||
srcs = ["SNTPrefixTree.cc"],
|
||||
hdrs = ["SNTPrefixTree.h"],
|
||||
copts = ["-std=c++11"],
|
||||
deps = [":SNTLogging"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "SNTPrefixTreeKernel",
|
||||
srcs = ["SNTPrefixTree.cc"],
|
||||
hdrs = ["SNTPrefixTree.h"],
|
||||
copts = [
|
||||
"-std=c++11",
|
||||
"-mkernel",
|
||||
"-I__BAZEL_XCODE_SDKROOT__/System/Library/Frameworks/Kernel.framework/Headers",
|
||||
],
|
||||
defines = ["KERNEL"],
|
||||
deps = [":SNTLoggingKernel"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTRule",
|
||||
srcs = ["SNTRule.m"],
|
||||
hdrs = ["SNTRule.h"],
|
||||
deps = [":SNTCommonEnums"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTStoredEvent",
|
||||
srcs = ["SNTStoredEvent.m"],
|
||||
hdrs = ["SNTStoredEvent.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
"@MOLCertificate",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "SNTStrengthify",
|
||||
hdrs = ["SNTStrengthify.h"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTSystemInfo",
|
||||
srcs = ["SNTSystemInfo.m"],
|
||||
hdrs = ["SNTSystemInfo.h"],
|
||||
sdk_frameworks = ["IOKit"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTXPCBundleServiceInterface",
|
||||
srcs = ["SNTXPCBundleServiceInterface.m"],
|
||||
hdrs = ["SNTXPCBundleServiceInterface.h"],
|
||||
deps = [
|
||||
":SNTStoredEvent",
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTXPCControlInterface",
|
||||
srcs = ["SNTXPCControlInterface.m"],
|
||||
hdrs = ["SNTXPCControlInterface.h"],
|
||||
deps = [
|
||||
":SNTConfigurator",
|
||||
":SNTStoredEvent",
|
||||
":SNTXPCUnprivilegedControlInterface",
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTXPCNotifierInterface",
|
||||
srcs = ["SNTXPCNotifierInterface.m"],
|
||||
hdrs = ["SNTXPCNotifierInterface.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTXPCBundleServiceInterface",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTXPCSyncdInterface",
|
||||
srcs = ["SNTXPCSyncdInterface.m"],
|
||||
hdrs = ["SNTXPCSyncdInterface.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTStoredEvent",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTXPCUnprivilegedControlInterface",
|
||||
srcs = ["SNTXPCUnprivilegedControlInterface.m"],
|
||||
hdrs = ["SNTXPCUnprivilegedControlInterface.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTKernelCommon",
|
||||
":SNTRule",
|
||||
":SNTStoredEvent",
|
||||
":SNTXPCBundleServiceInterface",
|
||||
"@MOLCertificate",
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "SNTFileInfoTest",
|
||||
srcs = ["SNTFileInfoTest.m"],
|
||||
resources = [
|
||||
"testdata/bad_pagezero",
|
||||
"testdata/missing_pagezero",
|
||||
],
|
||||
structured_resources = glob([
|
||||
"testdata/BundleExample.app/**",
|
||||
"testdata/DirectoryBundle/**",
|
||||
]),
|
||||
deps = [":SNTFileInfo"],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "SNTPrefixTreeTest",
|
||||
srcs = ["SNTPrefixTreeTest.mm"],
|
||||
deps = ["SNTPrefixTree"],
|
||||
)
|
||||
47
Source/common/SNTBlockMessage.h
Normal file
47
Source/common/SNTBlockMessage.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/// Copyright 2016 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#ifdef SANTAGUI
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#else
|
||||
#import <Foundation/Foundation.h>
|
||||
#endif
|
||||
|
||||
@class SNTStoredEvent;
|
||||
|
||||
@interface SNTBlockMessage : NSObject
|
||||
|
||||
///
|
||||
/// Return a message suitable for presenting to the user.
|
||||
/// Uses either the configured message depending on the event type or a custom message
|
||||
/// if the rule that blocked this file included one.
|
||||
///
|
||||
/// In SantaGUI this will return an NSAttributedString with links and formatting included
|
||||
/// while for santad all HTML will be properly stripped.
|
||||
///
|
||||
+ (NSAttributedString *)attributedBlockMessageForEvent:(SNTStoredEvent *)event
|
||||
customMessage:(NSString *)customMessage;
|
||||
|
||||
///
|
||||
/// Return a URL generated from the EventDetailURL configuration key
|
||||
/// after replacing templates in the URL with values from the event.
|
||||
///
|
||||
+ (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event;
|
||||
|
||||
///
|
||||
/// Strip HTML from a string, replacing <br /> with newline.
|
||||
///
|
||||
+ (NSString *)stringFromHTML:(NSString *)html;
|
||||
|
||||
@end
|
||||
129
Source/common/SNTBlockMessage.m
Normal file
129
Source/common/SNTBlockMessage.m
Normal file
@@ -0,0 +1,129 @@
|
||||
/// Copyright 2016 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/common/SNTBlockMessage.h"
|
||||
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
|
||||
@implementation SNTBlockMessage
|
||||
|
||||
+ (NSAttributedString *)attributedBlockMessageForEvent:(SNTStoredEvent *)event
|
||||
customMessage:(NSString *)customMessage {
|
||||
NSString *htmlHeader = @"<html><head><style>"
|
||||
@"body {"
|
||||
@" font-family: 'Lucida Grande', 'Helvetica', sans-serif;"
|
||||
@" font-size: 13px;"
|
||||
@" color: %@;"
|
||||
@" text-align: center;"
|
||||
@"}"
|
||||
|
||||
// Supported in beta WebKit. Not sure if it is dynamic when used with NSAttributedString.
|
||||
@"@media (prefers-color-scheme: dark) {"
|
||||
@" body {"
|
||||
@" color: #ddd;"
|
||||
@" }"
|
||||
@"}"
|
||||
@"</style></head><body>";
|
||||
|
||||
// Support Dark Mode. Note, the returned NSAttributedString is static and does not update when
|
||||
// the OS switches modes.
|
||||
NSString *mode = [NSUserDefaults.standardUserDefaults stringForKey:@"AppleInterfaceStyle"];
|
||||
BOOL dark = [mode isEqualToString:@"Dark"];
|
||||
htmlHeader = [NSString stringWithFormat:htmlHeader, dark ? @"#ddd" : @"#333"];
|
||||
|
||||
NSString *htmlFooter = @"</body></html>";
|
||||
|
||||
NSString *message;
|
||||
if (customMessage.length) {
|
||||
message = customMessage;
|
||||
} else if (event.decision == SNTEventStateBlockUnknown) {
|
||||
message = [[SNTConfigurator configurator] unknownBlockMessage];
|
||||
if (!message) {
|
||||
message = @"The following application has been blocked from executing<br />"
|
||||
@"because its trustworthiness cannot be determined.";
|
||||
}
|
||||
} else {
|
||||
message = [[SNTConfigurator configurator] bannedBlockMessage];
|
||||
if (!message) {
|
||||
message = @"The following application has been blocked from executing<br />"
|
||||
@"because it has been deemed malicious.";
|
||||
}
|
||||
}
|
||||
|
||||
NSString *fullHTML = [NSString stringWithFormat:@"%@%@%@", htmlHeader, message, htmlFooter];
|
||||
|
||||
#ifdef SANTAGUI
|
||||
NSData *htmlData = [fullHTML dataUsingEncoding:NSUTF8StringEncoding];
|
||||
return [[NSAttributedString alloc] initWithHTML:htmlData documentAttributes:NULL];
|
||||
#else
|
||||
NSString *strippedHTML = [self stringFromHTML:fullHTML];
|
||||
if (!strippedHTML) {
|
||||
return [[NSAttributedString alloc] initWithString:@"This binary has been blocked."];
|
||||
}
|
||||
return [[NSAttributedString alloc] initWithString:strippedHTML];
|
||||
#endif
|
||||
}
|
||||
|
||||
+ (NSString *)stringFromHTML:(NSString *)html {
|
||||
NSError *error;
|
||||
NSXMLDocument *xml = [[NSXMLDocument alloc] initWithXMLString:html options:0 error:&error];
|
||||
|
||||
if (!xml && error.code == NSXMLParserEmptyDocumentError) {
|
||||
html = [NSString stringWithFormat:@"<html><body>%@</body></html>", html];
|
||||
xml = [[NSXMLDocument alloc] initWithXMLString:html options:0 error:&error];
|
||||
if (!xml) return html;
|
||||
}
|
||||
|
||||
// Strip any HTML tags out of the message. Also remove any content inside <style> tags and
|
||||
// replace <br> elements with a newline.
|
||||
NSString *stripXslt = @"<?xml version='1.0' encoding='utf-8'?>"
|
||||
@"<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'"
|
||||
@" xmlns:xhtml='http://www.w3.org/1999/xhtml'>"
|
||||
@"<xsl:output method='text'/>"
|
||||
@"<xsl:template match='br'><xsl:text>\n</xsl:text></xsl:template>"
|
||||
@"<xsl:template match='style'/>"
|
||||
@"</xsl:stylesheet>";
|
||||
NSData *data = [xml objectByApplyingXSLTString:stripXslt arguments:NULL error:&error];
|
||||
if (error || ![data isKindOfClass:[NSData class]]) {
|
||||
return html;
|
||||
}
|
||||
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
+ (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event {
|
||||
SNTConfigurator *config = [SNTConfigurator configurator];
|
||||
|
||||
NSString *formatStr = config.eventDetailURL;
|
||||
if (!formatStr.length) return nil;
|
||||
|
||||
if (event.fileSHA256) {
|
||||
formatStr =
|
||||
[formatStr stringByReplacingOccurrencesOfString:@"%file_sha%"
|
||||
withString:event.fileBundleHash ?: event.fileSHA256];
|
||||
}
|
||||
if (event.executingUser) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%username%"
|
||||
withString:event.executingUser];
|
||||
}
|
||||
if (config.machineID) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%machine_id%"
|
||||
withString:config.machineID];
|
||||
}
|
||||
|
||||
return [NSURL URLWithString:formatStr];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -12,24 +12,30 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
///
|
||||
/// This is a simple ASN.1 decoder that utilizes Apple's SecAsn1Decode
|
||||
/// to parse the @c distinguishedNames property of NSURLProtectionSpace.
|
||||
///
|
||||
@interface SNTDERDecoder : NSObject
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@property(readonly) NSString *commonName;
|
||||
@property(readonly) NSString *organizationName;
|
||||
@property(readonly) NSString *organizationalUnit;
|
||||
@property(readonly) NSString *countryName;
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#import "Source/common/SNTKernelCommon.h"
|
||||
|
||||
@class MOLCertificate;
|
||||
|
||||
///
|
||||
/// Designated initializer.
|
||||
/// Store information about executions from decision making for later logging.
|
||||
///
|
||||
/// @param data one of the objects in the
|
||||
/// NSURLProtectionSpace.distinguishedNames array
|
||||
/// @return nil if decoding fails to find any expected objects
|
||||
///
|
||||
- (instancetype)initWithData:(NSData *)data;
|
||||
@interface SNTCachedDecision : NSObject
|
||||
|
||||
@property santa_vnode_id_t vnodeId;
|
||||
@property SNTEventState decision;
|
||||
@property NSString *decisionExtra;
|
||||
@property NSString *sha256;
|
||||
|
||||
@property NSString *certSHA256;
|
||||
@property NSString *certCommonName;
|
||||
@property NSArray<MOLCertificate *> *certChain;
|
||||
|
||||
@property NSString *quarantineURL;
|
||||
|
||||
@property NSString *customMsg;
|
||||
@property BOOL silentBlock;
|
||||
|
||||
@end
|
||||
@@ -12,14 +12,7 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTAppDelegate.h"
|
||||
#import "Source/common/SNTCachedDecision.h"
|
||||
|
||||
int main(int argc, const char *argv[]) {
|
||||
@autoreleasepool {
|
||||
NSApplication *app = [NSApplication sharedApplication];
|
||||
SNTAppDelegate *delegate = [[SNTAppDelegate alloc] init];
|
||||
[app setDelegate:delegate];
|
||||
[app finishLaunching];
|
||||
[app run];
|
||||
}
|
||||
}
|
||||
@implementation SNTCachedDecision
|
||||
@end
|
||||
@@ -1,111 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
///
|
||||
/// SNTCertificate wraps a @c SecCertificateRef to provide Objective-C accessors to
|
||||
/// commonly used certificate data. Accessors cache data for repeated access.
|
||||
///
|
||||
@interface SNTCertificate : NSObject<NSSecureCoding>
|
||||
|
||||
///
|
||||
/// Initialize a SNTCertificate object with a valid SecCertificateRef. Designated initializer.
|
||||
///
|
||||
/// @param certRef valid SecCertificateRef, which will be retained.
|
||||
///
|
||||
- (instancetype)initWithSecCertificateRef:(SecCertificateRef)certRef;
|
||||
|
||||
///
|
||||
/// Initialize a SNTCertificate object with certificate data in DER format.
|
||||
///
|
||||
/// @param certData DER-encoded certificate data.
|
||||
/// @return initialized SNTCertificate or nil if certData is not a DER-encoded certificate.
|
||||
///
|
||||
- (instancetype)initWithCertificateDataDER:(NSData *)certData;
|
||||
|
||||
///
|
||||
/// Initialize a SNTCertificate object with certificate data in PEM format.
|
||||
/// If multiple PEM certificates exist within the string, the first is used.
|
||||
///
|
||||
/// @param certData PEM-encoded certificate data.
|
||||
/// @return initialized SNTCertifcate or nil if certData is not a PEM-encoded certificate.
|
||||
///
|
||||
- (instancetype)initWithCertificateDataPEM:(NSString *)certData;
|
||||
|
||||
///
|
||||
/// Returns an array of SNTCertificate's for all of the certificates in @c pemData.
|
||||
///
|
||||
/// @param pemData PEM-encoded certificates.
|
||||
/// @return array of SNTCertificate objects.
|
||||
///
|
||||
+ (NSArray *)certificatesFromPEM:(NSString *)pemData;
|
||||
|
||||
///
|
||||
/// Access the underlying certificate ref.
|
||||
///
|
||||
@property(readonly, nonatomic) SecCertificateRef certRef;
|
||||
|
||||
///
|
||||
/// SHA-1 hash of the certificate data.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *SHA1;
|
||||
|
||||
///
|
||||
/// SHA-256 hash of the certificate data.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *SHA256;
|
||||
|
||||
///
|
||||
/// Certificate data.
|
||||
///
|
||||
@property(readonly, nonatomic) NSData *certData;
|
||||
|
||||
///
|
||||
/// Common Name e.g: "Software Signing"
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *commonName;
|
||||
|
||||
///
|
||||
/// Country Name e.g: "US"
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *countryName;
|
||||
|
||||
///
|
||||
/// Organizational Name e.g: "Apple Inc."
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *orgName;
|
||||
|
||||
///
|
||||
/// Organizational Unit Name e.g: "Apple Software"
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *orgUnit;
|
||||
|
||||
///
|
||||
/// Issuer details, same fields as above.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *issuerCommonName;
|
||||
@property(readonly, nonatomic) NSString *issuerCountryName;
|
||||
@property(readonly, nonatomic) NSString *issuerOrgName;
|
||||
@property(readonly, nonatomic) NSString *issuerOrgUnit;
|
||||
|
||||
///
|
||||
/// Validity Not Before
|
||||
///
|
||||
@property(readonly, nonatomic) NSDate *validFrom;
|
||||
|
||||
///
|
||||
/// Validity Not After
|
||||
///
|
||||
@property(readonly, nonatomic) NSDate *validUntil;
|
||||
|
||||
@end
|
||||
@@ -1,361 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTCertificate.h"
|
||||
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
#import <Security/Security.h>
|
||||
|
||||
@interface SNTCertificate ()
|
||||
/// A container for cached property values
|
||||
@property NSMutableDictionary *memoizedData;
|
||||
@end
|
||||
|
||||
@implementation SNTCertificate
|
||||
|
||||
static NSString *const kCertDataKey = @"certData";
|
||||
|
||||
#pragma mark Init/Dealloc
|
||||
|
||||
- (instancetype)initWithSecCertificateRef:(SecCertificateRef)certRef {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_certRef = certRef;
|
||||
CFRetain(_certRef);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCertificateDataDER:(NSData *)certData {
|
||||
SecCertificateRef cert = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData);
|
||||
|
||||
if (cert) {
|
||||
// Despite the header file claiming that SecCertificateCreateWithData will return NULL if
|
||||
// @c certData doesn't contain a valid DER-encoded X509 cert, this isn't always true.
|
||||
// radar://problem/16124651
|
||||
// To workaround, check that the certificate serial number can be retrieved. According to
|
||||
// RFC5280, the serial number field is required.
|
||||
NSData *ser = CFBridgingRelease(SecCertificateCopySerialNumber(cert, NULL));
|
||||
if (ser) {
|
||||
self = [self initWithSecCertificateRef:cert];
|
||||
} else {
|
||||
self = nil;
|
||||
}
|
||||
CFRelease(cert); // was retained in initWithSecCertificateRef
|
||||
} else {
|
||||
self = nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCertificateDataPEM:(NSString *)certData {
|
||||
// Find the PEM and extract the base64-encoded DER data from within
|
||||
NSScanner *scanner = [NSScanner scannerWithString:certData];
|
||||
NSString *base64der;
|
||||
|
||||
// Locate and parse DER data into |base64der|
|
||||
[scanner scanUpToString:@"-----BEGIN CERTIFICATE-----" intoString:NULL];
|
||||
if (!([scanner scanString:@"-----BEGIN CERTIFICATE-----" intoString:NULL] &&
|
||||
[scanner scanUpToString:@"-----END CERTIFICATE-----" intoString:&base64der] &&
|
||||
[scanner scanString:@"-----END CERTIFICATE-----" intoString:NULL])) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
// base64-decode the DER
|
||||
SecTransformRef transform = SecDecodeTransformCreate(kSecBase64Encoding, NULL);
|
||||
if (!transform) return nil;
|
||||
NSData *input = [base64der dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSData *output = nil;
|
||||
|
||||
if (SecTransformSetAttribute(transform,
|
||||
kSecTransformInputAttributeName,
|
||||
(__bridge CFDataRef)input,
|
||||
NULL)) {
|
||||
output = CFBridgingRelease(SecTransformExecute(transform, NULL));
|
||||
}
|
||||
if (transform) CFRelease(transform);
|
||||
|
||||
return [self initWithCertificateDataDER:output];
|
||||
}
|
||||
|
||||
+ (NSArray *)certificatesFromPEM:(NSString *)pemData {
|
||||
NSScanner *scanner = [NSScanner scannerWithString:pemData];
|
||||
NSMutableArray *certs = [[NSMutableArray alloc] init];
|
||||
|
||||
while (YES) {
|
||||
NSString *curCert;
|
||||
|
||||
[scanner scanUpToString:@"-----BEGIN CERTIFICATE-----" intoString:NULL];
|
||||
[scanner scanUpToString:@"-----END CERTIFICATE-----" intoString:&curCert];
|
||||
|
||||
// If there was no data, break.
|
||||
if (!curCert) break;
|
||||
|
||||
curCert = [curCert stringByAppendingString:@"-----END CERTIFICATE-----"];
|
||||
SNTCertificate *cert = [[SNTCertificate alloc] initWithCertificateDataPEM:curCert];
|
||||
|
||||
// If the data couldn't be turned into a valid SNTCertificate, continue.
|
||||
if (!cert) continue;
|
||||
|
||||
[certs addObject:cert];
|
||||
}
|
||||
|
||||
return certs;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
if (_certRef) CFRelease(_certRef);
|
||||
}
|
||||
|
||||
#pragma mark Equality & description
|
||||
|
||||
- (BOOL)isEqual:(id)other {
|
||||
if (self == other) return YES;
|
||||
if (![other isKindOfClass:[SNTCertificate class]]) return NO;
|
||||
|
||||
SNTCertificate *o = other;
|
||||
return [self.certData isEqual:o.certData];
|
||||
}
|
||||
|
||||
- (NSUInteger)hash {
|
||||
return [self.certData hash];
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return
|
||||
[NSString stringWithFormat:@"/O=%@/OU=%@/CN=%@", self.orgName, self.orgUnit, self.commonName];
|
||||
}
|
||||
|
||||
#pragma mark NSSecureCoding
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||
[coder encodeObject:self.certData forKey:kCertDataKey];
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||
NSData *certData = [decoder decodeObjectOfClass:[NSData class] forKey:kCertDataKey];
|
||||
if ([certData length] == 0) return nil;
|
||||
SecCertificateRef cert = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData);
|
||||
self = [self initWithSecCertificateRef:cert];
|
||||
if (cert) CFRelease(cert);
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark Private Accessors
|
||||
|
||||
///
|
||||
/// For a given selector, caches the value that selector would return on subsequent invocations,
|
||||
/// using the provided block to get the value on the first invocation.
|
||||
/// Assumes the selector's value will never change.
|
||||
///
|
||||
- (id)memoizedSelector:(SEL)selector forBlock:(id (^)(void))block {
|
||||
NSString *selName = NSStringFromSelector(selector);
|
||||
|
||||
if (!self.memoizedData) {
|
||||
self.memoizedData = [NSMutableDictionary dictionary];
|
||||
}
|
||||
|
||||
if (!self.memoizedData[selName]) {
|
||||
id val = block();
|
||||
if (val) {
|
||||
self.memoizedData[selName] = val;
|
||||
} else {
|
||||
self.memoizedData[selName] = [NSNull null];
|
||||
}
|
||||
}
|
||||
|
||||
// Return the value if there is one, or nil if the value is NSNull
|
||||
return self.memoizedData[selName] != [NSNull null] ? self.memoizedData[selName] : nil;
|
||||
}
|
||||
|
||||
- (NSDictionary *)allCertificateValues {
|
||||
return [self memoizedSelector:_cmd forBlock:^id{
|
||||
return CFBridgingRelease(SecCertificateCopyValues(self.certRef, NULL, NULL));
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSDictionary *)x509SubjectName {
|
||||
return [self memoizedSelector:_cmd forBlock:^id{
|
||||
return [self allCertificateValues][(__bridge NSString *)kSecOIDX509V1SubjectName];
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSDictionary *)x509IssuerName {
|
||||
return [self memoizedSelector:_cmd forBlock:^id{
|
||||
return [self allCertificateValues][(__bridge NSString *)kSecOIDX509V1IssuerName];
|
||||
}];
|
||||
}
|
||||
|
||||
///
|
||||
/// Retrieve the value with the specified label from the X509 dictionary provided
|
||||
///
|
||||
/// @param desiredLabel The label you want, e.g: kSecOIDOrganizationName.
|
||||
/// @param dict The dictionary to look in (Subject or Issuer)
|
||||
/// @return An @c NSString, the value for the specified label.
|
||||
///
|
||||
- (NSString *)x509ValueForLabel:(NSString *)desiredLabel fromDictionary:(NSDictionary *)dict {
|
||||
@try {
|
||||
NSArray *valArray = dict[(__bridge NSString *)kSecPropertyKeyValue];
|
||||
|
||||
for (NSDictionary *curCertVal in valArray) {
|
||||
NSString *valueLabel = curCertVal[(__bridge NSString *)kSecPropertyKeyLabel];
|
||||
if ([valueLabel isEqual:desiredLabel]) {
|
||||
return curCertVal[(__bridge NSString *)kSecPropertyKeyValue];
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
@catch (NSException *e) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Retrieve the specified date from the certificate's values and convert from a reference date
|
||||
/// to an NSDate object.
|
||||
///
|
||||
/// @param key The identifier for the date: @c kSecOIDX509V1ValiditityNot{Before,After}
|
||||
/// @return An @c NSDate representing the date and time the certificate is valid from or expires.
|
||||
///
|
||||
- (NSDate *)dateForX509Key:(NSString *)key {
|
||||
NSDictionary *curCertVal = [self allCertificateValues][key];
|
||||
NSNumber *value = curCertVal[(__bridge NSString *)kSecPropertyKeyValue];
|
||||
|
||||
NSTimeInterval interval = [value doubleValue];
|
||||
if (interval) {
|
||||
return [NSDate dateWithTimeIntervalSinceReferenceDate:interval];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
#pragma mark Public Accessors
|
||||
|
||||
- (NSString *)SHA1 {
|
||||
return [self memoizedSelector:_cmd forBlock:^id{
|
||||
NSMutableData *SHA1Buffer = [[NSMutableData alloc] initWithCapacity:CC_SHA1_DIGEST_LENGTH];
|
||||
|
||||
CC_SHA1([self.certData bytes], (CC_LONG)[self.certData length], [SHA1Buffer mutableBytes]);
|
||||
|
||||
const unsigned char *bytes = (const unsigned char *)[SHA1Buffer bytes];
|
||||
NSMutableString *hexDigest = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
|
||||
for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) {
|
||||
[hexDigest appendFormat:@"%02x", bytes[i]];
|
||||
}
|
||||
|
||||
return hexDigest;
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSString *)SHA256 {
|
||||
return [self memoizedSelector:_cmd forBlock:^id{
|
||||
NSMutableData *SHA256Buffer = [[NSMutableData alloc] initWithCapacity:CC_SHA256_DIGEST_LENGTH];
|
||||
|
||||
CC_SHA256([self.certData bytes], (CC_LONG)[self.certData length], [SHA256Buffer mutableBytes]);
|
||||
|
||||
const unsigned char *bytes = (const unsigned char *)[SHA256Buffer bytes];
|
||||
NSMutableString *hexDigest = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
|
||||
for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {
|
||||
[hexDigest appendFormat:@"%02x", bytes[i]];
|
||||
}
|
||||
|
||||
return hexDigest;
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSData *)certData {
|
||||
return CFBridgingRelease(SecCertificateCopyData(self.certRef));
|
||||
}
|
||||
|
||||
- (NSString *)commonName {
|
||||
return [self memoizedSelector:_cmd forBlock:^id{
|
||||
CFStringRef commonName = NULL;
|
||||
SecCertificateCopyCommonName(self.certRef, &commonName);
|
||||
return CFBridgingRelease(commonName);
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSString *)countryName {
|
||||
return [self memoizedSelector:_cmd forBlock:^id{
|
||||
return [self x509ValueForLabel:(__bridge NSString *)kSecOIDCountryName
|
||||
fromDictionary:[self x509SubjectName]];
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSString *)orgName {
|
||||
return [self memoizedSelector:_cmd forBlock:^id{
|
||||
return [self x509ValueForLabel:(__bridge NSString *)kSecOIDOrganizationName
|
||||
fromDictionary:[self x509SubjectName]];
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSString *)orgUnit {
|
||||
return [self memoizedSelector:_cmd forBlock:^id{
|
||||
return [self x509ValueForLabel:(__bridge NSString *)kSecOIDOrganizationalUnitName
|
||||
fromDictionary:[self x509SubjectName]];
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSDate *)validFrom {
|
||||
return [self memoizedSelector:_cmd forBlock:^id{
|
||||
return [self dateForX509Key:(__bridge NSString *)kSecOIDX509V1ValidityNotBefore];
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSDate *)validUntil {
|
||||
return [self memoizedSelector:_cmd forBlock:^id{
|
||||
return [self dateForX509Key:(__bridge NSString *)kSecOIDX509V1ValidityNotAfter];
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSString *)issuerCommonName {
|
||||
return [self memoizedSelector:_cmd forBlock:^id{
|
||||
return [self x509ValueForLabel:(__bridge NSString *)kSecOIDCommonName
|
||||
fromDictionary:[self x509IssuerName]];
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSString *)issuerCountryName {
|
||||
return [self memoizedSelector:_cmd forBlock:^id{
|
||||
return [self x509ValueForLabel:(__bridge NSString *)kSecOIDCountryName
|
||||
fromDictionary:[self x509IssuerName]];
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSString *)issuerOrgName {
|
||||
return [self memoizedSelector:_cmd forBlock:^id{
|
||||
return [self x509ValueForLabel:(__bridge NSString *)kSecOIDOrganizationName
|
||||
fromDictionary:[self x509IssuerName]];
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSString *)issuerOrgUnit {
|
||||
return [self memoizedSelector:_cmd forBlock:^id{
|
||||
return [self x509ValueForLabel:(__bridge NSString *)kSecOIDOrganizationalUnitName
|
||||
fromDictionary:[self x509IssuerName]];
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -1,90 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
@class SNTCertificate;
|
||||
|
||||
///
|
||||
/// SNTCodesignChecker validates a binary (either on-disk or in memory) has been signed
|
||||
/// and if so allows for pulling out the certificates that were used to sign it.
|
||||
///
|
||||
@interface SNTCodesignChecker : NSObject
|
||||
|
||||
///
|
||||
/// The SecStaticCodeRef that this SNTCodesignChecker is working around
|
||||
///
|
||||
@property(readonly) SecStaticCodeRef codeRef;
|
||||
|
||||
///
|
||||
/// Returns a dictionary of raw signing information
|
||||
///
|
||||
@property(readonly) NSDictionary *signingInformation;
|
||||
|
||||
///
|
||||
/// Returns an array of @c SNTCertificate objects representing the chain that signed this binary.
|
||||
///
|
||||
@property(readonly) NSArray *certificates;
|
||||
|
||||
///
|
||||
/// Returns the leaf certificate that this binary was signed with
|
||||
///
|
||||
@property(readonly, nonatomic) SNTCertificate *leafCertificate;
|
||||
|
||||
///
|
||||
/// Returns the on-disk path of this binary.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *binaryPath;
|
||||
|
||||
///
|
||||
/// Designated initializer
|
||||
/// Takes ownership of the codeRef reference.
|
||||
///
|
||||
/// @param codeRef a SecStaticCodeRef or SecCodeRef representing a binary.
|
||||
/// @return an initialized SNTCodesignChecker if the binary is validly signed, nil otherwise.
|
||||
///
|
||||
- (instancetype)initWithSecStaticCodeRef:(SecStaticCodeRef)codeRef;
|
||||
|
||||
///
|
||||
/// Convenience initializer for a binary on disk.
|
||||
///
|
||||
/// @param binaryPath A binary file on disk
|
||||
/// @return an initialized SNTCodesignChecker if file is a binary and is signed, nil otherwise.
|
||||
///
|
||||
- (instancetype)initWithBinaryPath:(NSString *)binaryPath;
|
||||
|
||||
///
|
||||
/// Convenience initializer for a binary that is running, by its process ID.
|
||||
///
|
||||
/// @param PID Id of a running process.
|
||||
/// @return an initialized SNTCodesignChecker if binary is signed, nil otherwise.
|
||||
///
|
||||
- (instancetype)initWithPID:(pid_t)PID;
|
||||
|
||||
///
|
||||
/// Convenience initializer for the currently running process.
|
||||
///
|
||||
/// @return an initialized SNTCodesignChecker if current binary is signed, nil otherwise.
|
||||
///
|
||||
- (instancetype)initWithSelf;
|
||||
|
||||
///
|
||||
/// Compares the signatures of the binaries represented by this SNTCodesignChecker and
|
||||
/// @c otherChecker.
|
||||
///
|
||||
/// If both binaries are correctly signed and the leaf signatures are identical.
|
||||
///
|
||||
/// @return YES if both binaries are signed with the same leaf certificate.
|
||||
///
|
||||
- (BOOL)signingInformationMatches:(SNTCodesignChecker *)otherChecker;
|
||||
|
||||
@end
|
||||
@@ -1,193 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTCodesignChecker.h"
|
||||
|
||||
#import <Security/Security.h>
|
||||
|
||||
#import "SNTCertificate.h"
|
||||
|
||||
/**
|
||||
* kStaticSigningFlags are the flags used when validating signatures on disk.
|
||||
*
|
||||
* Don't validate resources but do validate nested code. Ignoring resources _dramatically_ speeds
|
||||
* up validation (see below) but does mean images, plists, etc will not be checked and modifying
|
||||
* these will not be considered invalid. To ensure any code inside the binary is still checked,
|
||||
* we check nested code.
|
||||
*
|
||||
* Timings with different flags:
|
||||
* Checking Xcode 5.1.1 bundle:
|
||||
* kSecCSDefaultFlags: 3.895s
|
||||
* kSecCSDoNotValidateResources: 0.013s
|
||||
* kSecCSDoNotValidateResources | kSecCSCheckNestedCode: 0.013s
|
||||
*
|
||||
* Checking Google Chrome 36.0.1985.143 bundle:
|
||||
* kSecCSDefaultFlags: 0.529s
|
||||
* kSecCSDoNotValidateResources: 0.032s
|
||||
* kSecCSDoNotValidateResources | kSecCSCheckNestedCode: 0.033s
|
||||
*/
|
||||
static const SecCSFlags kStaticSigningFlags = kSecCSDoNotValidateResources | kSecCSCheckNestedCode;
|
||||
|
||||
/**
|
||||
* kSigningFlags are the flags used when validating signatures for running binaries.
|
||||
*
|
||||
* No special flags needed currently.
|
||||
*/
|
||||
static const SecCSFlags kSigningFlags = kSecCSDefaultFlags;
|
||||
|
||||
@interface SNTCodesignChecker ()
|
||||
/// Array of @c SNTCertificate's representing the chain of certs this executable was signed with.
|
||||
@property NSMutableArray *certificates;
|
||||
@end
|
||||
|
||||
@implementation SNTCodesignChecker
|
||||
|
||||
#pragma mark Init/dealloc
|
||||
|
||||
- (instancetype)initWithSecStaticCodeRef:(SecStaticCodeRef)codeRef {
|
||||
self = [super init];
|
||||
|
||||
if (self) {
|
||||
// First check the signing is valid
|
||||
if (CFGetTypeID(codeRef) == SecStaticCodeGetTypeID()) {
|
||||
if (SecStaticCodeCheckValidity(codeRef, kStaticSigningFlags, NULL) != errSecSuccess) {
|
||||
return nil;
|
||||
}
|
||||
} else if (CFGetTypeID(codeRef) == SecCodeGetTypeID()) {
|
||||
if (SecCodeCheckValidity((SecCodeRef)codeRef, kSigningFlags, NULL) != errSecSuccess) {
|
||||
return nil;
|
||||
}
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Get CFDictionary of signing information for binary
|
||||
OSStatus status = errSecSuccess;
|
||||
CFDictionaryRef signingDict = NULL;
|
||||
status = SecCodeCopySigningInformation(codeRef, kSecCSSigningInformation, &signingDict);
|
||||
_signingInformation = CFBridgingRelease(signingDict);
|
||||
if (status != errSecSuccess) return nil;
|
||||
|
||||
// Get array of certificates.
|
||||
NSArray *certs = _signingInformation[(id)kSecCodeInfoCertificates];
|
||||
if (!certs) return nil;
|
||||
|
||||
// Wrap SecCertificateRef objects in SNTCertificate and put in a new NSArray
|
||||
NSMutableArray *mutableCerts = [[NSMutableArray alloc] initWithCapacity:certs.count];
|
||||
for (NSUInteger i = 0; i < certs.count; ++i) {
|
||||
SecCertificateRef certRef = (__bridge SecCertificateRef)certs[i];
|
||||
SNTCertificate *newCert = [[SNTCertificate alloc] initWithSecCertificateRef:certRef];
|
||||
[mutableCerts addObject:newCert];
|
||||
}
|
||||
_certificates = [mutableCerts copy];
|
||||
|
||||
_codeRef = codeRef;
|
||||
CFRetain(_codeRef);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBinaryPath:(NSString *)binaryPath {
|
||||
SecStaticCodeRef codeRef = NULL;
|
||||
|
||||
// Get SecStaticCodeRef for binary
|
||||
if (SecStaticCodeCreateWithPath(
|
||||
(__bridge CFURLRef)[NSURL fileURLWithPath:binaryPath isDirectory:NO],
|
||||
kSecCSDefaultFlags,
|
||||
&codeRef) == errSecSuccess) {
|
||||
self = [self initWithSecStaticCodeRef:codeRef];
|
||||
} else {
|
||||
self = nil;
|
||||
}
|
||||
|
||||
if (codeRef) CFRelease(codeRef);
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithPID:(pid_t)PID {
|
||||
SecCodeRef codeRef = NULL;
|
||||
NSDictionary *attributes = @{ (__bridge NSString *)kSecGuestAttributePid : @(PID) };
|
||||
|
||||
if (SecCodeCopyGuestWithAttributes(
|
||||
NULL,
|
||||
(__bridge CFDictionaryRef)attributes,
|
||||
kSecCSDefaultFlags,
|
||||
&codeRef) == errSecSuccess) {
|
||||
self = [self initWithSecStaticCodeRef:codeRef];
|
||||
} else {
|
||||
self = nil;
|
||||
}
|
||||
|
||||
if (codeRef) CFRelease(codeRef);
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithSelf {
|
||||
SecCodeRef codeSelf = NULL;
|
||||
if (SecCodeCopySelf(kSecCSDefaultFlags, &codeSelf) == errSecSuccess) {
|
||||
self = [self initWithSecStaticCodeRef:codeSelf];
|
||||
} else {
|
||||
self = nil;
|
||||
}
|
||||
|
||||
if (codeSelf) CFRelease(codeSelf);
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
if (_codeRef) {
|
||||
CFRelease(_codeRef);
|
||||
_codeRef = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark Description
|
||||
|
||||
- (NSString *)description {
|
||||
NSString *binarySource;
|
||||
if (CFGetTypeID(self.codeRef) == SecStaticCodeGetTypeID()) {
|
||||
binarySource = @"On-disk";
|
||||
} else {
|
||||
binarySource = @"In-memory";
|
||||
}
|
||||
|
||||
return [NSString stringWithFormat:@"%@ binary, signed by %@, located at: %@",
|
||||
binarySource, self.leafCertificate.orgName, self.binaryPath];
|
||||
}
|
||||
|
||||
#pragma mark Public accessors
|
||||
|
||||
- (SNTCertificate *)leafCertificate {
|
||||
return [self.certificates firstObject];
|
||||
}
|
||||
|
||||
- (NSString *)binaryPath {
|
||||
CFURLRef path;
|
||||
OSStatus status = SecCodeCopyPath(self.codeRef, kSecCSDefaultFlags, &path);
|
||||
NSURL *pathURL = CFBridgingRelease(path);
|
||||
if (status != errSecSuccess) return nil;
|
||||
return [pathURL path];
|
||||
}
|
||||
|
||||
- (BOOL)signingInformationMatches:(SNTCodesignChecker *)otherChecker {
|
||||
return [self.certificates isEqual:otherChecker.certificates];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -12,57 +12,86 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#ifndef SANTA__COMMON__COMMONENUMS_H
|
||||
#define SANTA__COMMON__COMMONENUMS_H
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
///
|
||||
/// These enums are used in various places throughout the Santa client code.
|
||||
/// The integer values are also stored in the database and so shouldn't be changed.
|
||||
///
|
||||
|
||||
typedef enum {
|
||||
RULETYPE_UNKNOWN,
|
||||
typedef NS_ENUM(NSInteger, SNTRuleType) {
|
||||
SNTRuleTypeUnknown,
|
||||
|
||||
RULETYPE_BINARY = 1,
|
||||
RULETYPE_CERT = 2,
|
||||
SNTRuleTypeBinary = 1,
|
||||
SNTRuleTypeCertificate = 2,
|
||||
};
|
||||
|
||||
RULETYPE_MAX
|
||||
} santa_ruletype_t;
|
||||
typedef NS_ENUM(NSInteger, SNTRuleState) {
|
||||
SNTRuleStateUnknown,
|
||||
|
||||
typedef enum {
|
||||
RULESTATE_UNKNOWN,
|
||||
SNTRuleStateWhitelist = 1,
|
||||
SNTRuleStateBlacklist = 2,
|
||||
SNTRuleStateSilentBlacklist = 3,
|
||||
SNTRuleStateRemove = 4,
|
||||
|
||||
RULESTATE_WHITELIST = 1,
|
||||
RULESTATE_BLACKLIST = 2,
|
||||
RULESTATE_SILENT_BLACKLIST = 3,
|
||||
RULESTATE_REMOVE = 4,
|
||||
SNTRuleStateWhitelistCompiler = 5,
|
||||
SNTRuleStateWhitelistTransitive = 6,
|
||||
};
|
||||
|
||||
RULESTATE_MAX
|
||||
} santa_rulestate_t;
|
||||
typedef NS_ENUM(NSInteger, SNTClientMode) {
|
||||
SNTClientModeUnknown,
|
||||
|
||||
typedef enum {
|
||||
CLIENTMODE_UNKNOWN,
|
||||
SNTClientModeMonitor = 1,
|
||||
SNTClientModeLockdown = 2,
|
||||
};
|
||||
|
||||
CLIENTMODE_MONITOR = 1,
|
||||
CLIENTMODE_LOCKDOWN = 2,
|
||||
typedef NS_ENUM(NSInteger, SNTEventState) {
|
||||
// Bits 0-15 bits store non-decision types
|
||||
SNTEventStateUnknown = 0,
|
||||
SNTEventStateBundleBinary = 1,
|
||||
|
||||
CLIENTMODE_MAX
|
||||
} santa_clientmode_t;
|
||||
// Bits 16-23 store deny decision types
|
||||
SNTEventStateBlockUnknown = 1 << 16,
|
||||
SNTEventStateBlockBinary = 1 << 17,
|
||||
SNTEventStateBlockCertificate = 1 << 18,
|
||||
SNTEventStateBlockScope = 1 << 19,
|
||||
|
||||
typedef enum {
|
||||
EVENTSTATE_UNKNOWN,
|
||||
// Bits 24-31 store allow decision types
|
||||
SNTEventStateAllowUnknown = 1 << 24,
|
||||
SNTEventStateAllowBinary = 1 << 25,
|
||||
SNTEventStateAllowCertificate = 1 << 26,
|
||||
SNTEventStateAllowScope = 1 << 27,
|
||||
SNTEventStateAllowCompiler = 1 << 28,
|
||||
SNTEventStateAllowTransitive = 1 << 29,
|
||||
SNTEventStateAllowPendingTransitive = 1 << 30,
|
||||
|
||||
EVENTSTATE_ALLOW_UNKNOWN = 1,
|
||||
EVENTSTATE_ALLOW_BINARY = 2,
|
||||
EVENTSTATE_ALLOW_CERTIFICATE = 3,
|
||||
EVENTSTATE_ALLOW_SCOPE = 4,
|
||||
// Block and Allow masks
|
||||
SNTEventStateBlock = 0xFF << 16,
|
||||
SNTEventStateAllow = 0xFF << 24
|
||||
};
|
||||
|
||||
EVENTSTATE_BLOCK_UNKNOWN = 5,
|
||||
EVENTSTATE_BLOCK_BINARY = 6,
|
||||
EVENTSTATE_BLOCK_CERTIFICATE = 7,
|
||||
EVENTSTATE_BLOCK_SCOPE = 8,
|
||||
typedef NS_ENUM(NSInteger, SNTRuleTableError) {
|
||||
SNTRuleTableErrorEmptyRuleArray,
|
||||
SNTRuleTableErrorInsertOrReplaceFailed,
|
||||
SNTRuleTableErrorInvalidRule,
|
||||
SNTRuleTableErrorRemoveFailed
|
||||
};
|
||||
|
||||
EVENTSTATE_MAX
|
||||
} santa_eventstate_t;
|
||||
// This enum type is used to indicate what should be done with the related bundle events that are
|
||||
// generated when an initiating blocked bundle event occurs.
|
||||
typedef NS_ENUM(NSInteger, SNTBundleEventAction) {
|
||||
SNTBundleEventActionDropEvents,
|
||||
SNTBundleEventActionStoreEvents,
|
||||
SNTBundleEventActionSendEvents,
|
||||
};
|
||||
|
||||
#endif // SANTA__COMMON__COMMONENUMS_H
|
||||
// Indicates where to store event logs.
|
||||
typedef NS_ENUM(NSInteger, SNTEventLogType) {
|
||||
SNTEventLogTypeSyslog,
|
||||
SNTEventLogTypeFilelog,
|
||||
};
|
||||
|
||||
static const char *kKextPath = "/Library/Extensions/santa-driver.kext";
|
||||
static const char *kSantaDPath = "/Applications/Santa.app/Contents/Library/SystemExtensions/com.google.santa.daemon.systemextension/Contents/MacOS/com.google.santa.daemon";
|
||||
static const char *kSantaCtlPath = "/Applications/Santa.app/Contents/MacOS/santactl";
|
||||
static const char *kSantaAppPath = "/Applications/Santa.app";
|
||||
|
||||
@@ -12,28 +12,166 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include "SNTCommonEnums.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
|
||||
///
|
||||
/// Singleton that provides an interface for managing configuration values on disk
|
||||
/// @note This class is designed as a singleton but that is not strictly enforced.
|
||||
/// @note All properties are KVO compliant.
|
||||
///
|
||||
@interface SNTConfigurator : NSObject
|
||||
|
||||
/// Default config file path
|
||||
extern NSString * const kDefaultConfigFilePath;
|
||||
|
||||
#pragma mark - Daemon Settings
|
||||
|
||||
///
|
||||
/// The operating mode.
|
||||
///
|
||||
@property(nonatomic) santa_clientmode_t clientMode;
|
||||
@property(readonly, nonatomic) SNTClientMode clientMode;
|
||||
|
||||
///
|
||||
/// Whether or not to log all events, even for whitelisted binaries.
|
||||
/// Set the operating mode as received from a sync server.
|
||||
///
|
||||
@property(nonatomic) BOOL logAllEvents;
|
||||
- (void)setSyncServerClientMode:(SNTClientMode)newMode;
|
||||
|
||||
///
|
||||
/// The regex of whitelisted paths. Regexes are specified in ICU format.
|
||||
///
|
||||
/// The regex flags IXSM can be used, though the s (dotall) and m (multiline) flags are
|
||||
/// pointless as a path only ever has a single line.
|
||||
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
|
||||
///
|
||||
@property(readonly, nonatomic) NSRegularExpression *whitelistPathRegex;
|
||||
|
||||
///
|
||||
/// Set the regex of whitelisted paths as received from a sync server.
|
||||
///
|
||||
- (void)setSyncServerWhitelistPathRegex:(NSRegularExpression *)re;
|
||||
|
||||
///
|
||||
/// The regex of blacklisted paths. Regexes are specified in ICU format.
|
||||
///
|
||||
/// The regex flags IXSM can be used, though the s (dotall) and m (multiline) flags are
|
||||
/// pointless as a path only ever has a single line.
|
||||
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
|
||||
///
|
||||
@property(readonly, nonatomic) NSRegularExpression *blacklistPathRegex;
|
||||
|
||||
///
|
||||
/// Set the regex of blacklisted paths as received from a sync server.
|
||||
///
|
||||
- (void)setSyncServerBlacklistPathRegex:(NSRegularExpression *)re;
|
||||
|
||||
///
|
||||
/// The regex of paths to log file changes for. Regexes are specified in ICU format.
|
||||
///
|
||||
/// The regex flags IXSM can be used, though the s (dotalL) and m (multiline) flags are
|
||||
/// pointless as a path only ever has a single line.
|
||||
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
|
||||
///
|
||||
@property(readonly, nonatomic) NSRegularExpression *fileChangesRegex;
|
||||
|
||||
///
|
||||
/// A list of ignore prefixes which are checked in-kernel.
|
||||
/// This is more performant than FileChangesRegex when ignoring whole directory trees.
|
||||
///
|
||||
/// For example adding a prefix of "/private/tmp/" will turn off file change log generation
|
||||
/// in-kernel for that entire tree. Since they are ignored by the kernel, they never reach santad
|
||||
/// and are not seen by the fileChangesRegex. Note the trailing "/", without it any file or
|
||||
/// directory starting with "/private/tmp" would be ignored.
|
||||
///
|
||||
/// By default "/." and "/dev/" are added.
|
||||
///
|
||||
/// Memory in the kernel is precious. A total of MAXPATHLEN (1024) nodes are allowed.
|
||||
/// Using all 1024 nodes will result in santa-driver allocating ~2MB of wired memory.
|
||||
/// An ASCII character uses 1 node. An UTF-8 encoded Unicode character uses 1-4 nodes.
|
||||
/// Prefixes are added to the running config in-order, one by one. The prefix will be ignored if
|
||||
/// (the running config's current size) + (the prefix's size) totals up to more than 1024 nodes.
|
||||
/// The running config is stored in a prefix tree.
|
||||
/// Prefixes that share prefixes are effectively de-duped; their shared node sized components only
|
||||
/// take up 1 node. For example these 3 prefixes all have a common prefix of "/private/".
|
||||
/// They will only take up 21 nodes instead of 39.
|
||||
///
|
||||
/// "/private/tmp/"
|
||||
/// "/private/var/"
|
||||
/// "/private/new/"
|
||||
///
|
||||
/// -> [t] -> [m] -> [p] -> [/]
|
||||
///
|
||||
/// [/] -> [p] -> [r] -> [i] -> [v] -> [a] -> [t] -> [e] -> [/] -> [v] -> [a] -> [r] -> [/]
|
||||
///
|
||||
/// -> [n] -> [e] -> [w] -> [/]
|
||||
///
|
||||
/// Prefixes with Unicode characters work similarly. Assuming a UTF-8 encoding these two prefixes
|
||||
/// are actually the same for the first 3 nodes. They take up 7 nodes instead of 10.
|
||||
///
|
||||
/// "/🤘"
|
||||
/// "/🖖"
|
||||
///
|
||||
/// -> [0xa4] -> [0x98]
|
||||
///
|
||||
/// [/] -> [0xf0] -> [0x9f]
|
||||
///
|
||||
/// -> [0x96] -> [0x96]
|
||||
///
|
||||
/// To disable file change logging completely add "/".
|
||||
/// TODO(bur): Make this default if no FileChangesRegex is set.
|
||||
///
|
||||
/// Filters are only applied on santad startup.
|
||||
/// TODO(bur): Support add / remove of filters while santad is running.
|
||||
///
|
||||
@property(readonly, nonatomic) NSArray *fileChangesPrefixFilters;
|
||||
|
||||
///
|
||||
/// Enable __PAGEZERO protection, defaults to YES
|
||||
/// If this flag is set to NO, 32-bit binaries that are missing
|
||||
/// the __PAGEZERO segment will not be blocked.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enablePageZeroProtection;
|
||||
|
||||
///
|
||||
/// Enable bad signature protection, defaults to NO.
|
||||
/// When enabled, a binary that is signed but has a bad signature (cert revoked, binary
|
||||
/// tampered with, etc.) will be blocked regardless of client-mode unless a binary whitelist
|
||||
/// rule exists.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enableBadSignatureProtection;
|
||||
|
||||
///
|
||||
/// Defines how event logs are stored. Options are:
|
||||
/// SNTEventLogTypeSyslog: Sent to ASL or ULS (if built with the 10.12 SDK or later).
|
||||
/// SNTEventLogTypeFilelog: Sent to a file on disk. Use eventLogPath to specify a path.
|
||||
/// Defaults to SNTEventLogTypeFilelog.
|
||||
/// For mobileconfigs use EventLogType as the key and syslog or filelog strings as the value.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) SNTEventLogType eventLogType;
|
||||
|
||||
///
|
||||
/// If eventLogType is set to Filelog, eventLogPath will provide the path to save logs.
|
||||
/// Defaults to /var/db/santa/santa.log.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *eventLogPath;
|
||||
|
||||
///
|
||||
/// Enabling this appends the Santa machine ID to the end of each log line. If nothing
|
||||
/// has been overriden, this is the host's UUID.
|
||||
/// Defaults to NO.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enableMachineIDDecoration;
|
||||
|
||||
///
|
||||
/// Use the bundled SystemExtension on macOS 10.15+, defaults to YES.
|
||||
/// Disable to continue using the bundled KEXT.
|
||||
/// This is a one way switch, if this is ever true on macOS 10.15+ the KEXT will be deleted.
|
||||
/// This gives admins control over the timing of switching to the SystemExtension. The intended use case is to have an MDM deliver
|
||||
/// the requisite SystemExtension and TCC profiles before attempting to load.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enableSystemExtension;
|
||||
|
||||
#pragma mark - GUI Settings
|
||||
|
||||
@@ -46,6 +184,7 @@ extern NSString * const kDefaultConfigFilePath;
|
||||
///
|
||||
/// When the user gets a block notification, a button can be displayed which will
|
||||
/// take them to a web page with more information about that event.
|
||||
///
|
||||
/// This property contains a kind of format string to be turned into the URL to send them to.
|
||||
/// The following sequences will be replaced in the final URL:
|
||||
///
|
||||
@@ -64,6 +203,31 @@ extern NSString * const kDefaultConfigFilePath;
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *eventDetailText;
|
||||
|
||||
///
|
||||
/// In lockdown mode this is the message shown to the user when an unknown binary
|
||||
/// is blocked. If this message is not configured, a reasonable default is provided.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *unknownBlockMessage;
|
||||
|
||||
///
|
||||
/// This is the message shown to the user when a binary is blocked because of a rule,
|
||||
/// if that rule doesn't provide a custom message. If this is not configured, a reasonable
|
||||
/// default is provided.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *bannedBlockMessage;
|
||||
|
||||
///
|
||||
/// The notification text to display when the client goes into MONITOR mode.
|
||||
/// Defaults to "Switching into Monitor mode"
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *modeNotificationMonitor;
|
||||
|
||||
///
|
||||
/// The notification text to display when the client goes into LOCKDOWN mode.
|
||||
/// Defaults to "Switching into Lockdown mode"
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *modeNotificationLockdown;
|
||||
|
||||
#pragma mark - Sync Settings
|
||||
|
||||
///
|
||||
@@ -76,11 +240,41 @@ extern NSString * const kDefaultConfigFilePath;
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *machineOwner;
|
||||
|
||||
///
|
||||
/// The last date of a successful full sync.
|
||||
///
|
||||
@property(nonatomic) NSDate *fullSyncLastSuccess;
|
||||
|
||||
///
|
||||
/// The last date of a successful rule sync.
|
||||
///
|
||||
@property(nonatomic) NSDate *ruleSyncLastSuccess;
|
||||
|
||||
///
|
||||
/// If YES a clean sync is required.
|
||||
///
|
||||
@property(nonatomic) BOOL syncCleanRequired;
|
||||
|
||||
///
|
||||
/// If set, this over-rides the default machine ID used for syncing.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *machineID;
|
||||
|
||||
///
|
||||
/// If YES, enables bundle detection for blocked events. This property is not stored on disk.
|
||||
/// Its value is set by a sync server that supports bundles. Defaults to NO.
|
||||
///
|
||||
@property BOOL enableBundles;
|
||||
|
||||
#pragma mark Transitive Whitelisting Settings
|
||||
|
||||
///
|
||||
/// If YES, binaries marked with SNTRuleStateWhitelistCompiler rules are allowed to transitively
|
||||
/// whitelist any executables that they produce. If NO, SNTRuleStateWhitelistCompiler rules are
|
||||
/// interpreted as if they were simply SNTRuleStateWhitelist rules. Defaults to NO.
|
||||
///
|
||||
@property BOOL enableTransitiveWhitelisting;
|
||||
|
||||
#pragma mark Server Auth Settings
|
||||
|
||||
///
|
||||
@@ -124,15 +318,8 @@ extern NSString * const kDefaultConfigFilePath;
|
||||
+ (instancetype)configurator;
|
||||
|
||||
///
|
||||
/// Designated initializer.
|
||||
/// Clear the sync server configuration from the effective configuration.
|
||||
///
|
||||
/// @param filePath The path to the file to use as a backing store.
|
||||
///
|
||||
- (instancetype)initWithFilePath:(NSString *)filePath;
|
||||
|
||||
///
|
||||
/// Re-read config data from disk.
|
||||
///
|
||||
- (void)reloadConfigData;
|
||||
- (void)clearSyncState;
|
||||
|
||||
@end
|
||||
|
||||
@@ -12,52 +12,140 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTConfigurator.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
|
||||
#import "SNTLogging.h"
|
||||
#import "SNTSystemInfo.h"
|
||||
#include <sys/stat.h>
|
||||
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/common/SNTStrengthify.h"
|
||||
#import "Source/common/SNTSystemInfo.h"
|
||||
|
||||
@interface SNTConfigurator ()
|
||||
@property NSString *configFilePath;
|
||||
@property NSMutableDictionary *configData;
|
||||
/// A NSUserDefaults object set to use the com.google.santa suite.
|
||||
@property(readonly, nonatomic) NSUserDefaults *defaults;
|
||||
|
||||
// Keys and expected value types.
|
||||
@property(readonly, nonatomic) NSDictionary *syncServerKeyTypes;
|
||||
@property(readonly, nonatomic) NSDictionary *forcedConfigKeyTypes;
|
||||
|
||||
/// Holds the configurations from a sync server and mobileconfig.
|
||||
@property NSMutableDictionary *syncState;
|
||||
@property NSMutableDictionary *configState;
|
||||
@end
|
||||
|
||||
@implementation SNTConfigurator
|
||||
|
||||
/// The hard-coded path to the config file
|
||||
NSString * const kDefaultConfigFilePath = @"/var/db/santa/config.plist";
|
||||
/// The hard-coded path to the sync state file.
|
||||
NSString *const kSyncStateFilePath = @"/var/db/santa/sync-state.plist";
|
||||
|
||||
/// The keys in the config file
|
||||
static NSString * const kClientModeKey = @"ClientMode";
|
||||
/// The domain used by mobileconfig.
|
||||
static NSString *const kMobileConfigDomain = @"com.google.santa";
|
||||
|
||||
static NSString * const kLogAllEventsKey = @"LogAllEvents";
|
||||
/// The keys managed by a mobileconfig.
|
||||
static NSString *const kSyncBaseURLKey = @"SyncBaseURL";
|
||||
static NSString *const kClientAuthCertificateFileKey = @"ClientAuthCertificateFile";
|
||||
static NSString *const kClientAuthCertificatePasswordKey = @"ClientAuthCertificatePassword";
|
||||
static NSString *const kClientAuthCertificateCNKey = @"ClientAuthCertificateCN";
|
||||
static NSString *const kClientAuthCertificateIssuerKey = @"ClientAuthCertificateIssuerCN";
|
||||
static NSString *const kServerAuthRootsDataKey = @"ServerAuthRootsData";
|
||||
static NSString *const kServerAuthRootsFileKey = @"ServerAuthRootsFile";
|
||||
|
||||
static NSString * const kMoreInfoURLKey = @"MoreInfoURL";
|
||||
static NSString * const kEventDetailURLKey = @"EventDetailURL";
|
||||
static NSString * const kEventDetailTextKey = @"EventDetailText";
|
||||
static NSString *const kMachineOwnerKey = @"MachineOwner";
|
||||
static NSString *const kMachineIDKey = @"MachineID";
|
||||
static NSString *const kMachineOwnerPlistFileKey = @"MachineOwnerPlist";
|
||||
static NSString *const kMachineOwnerPlistKeyKey = @"MachineOwnerKey";
|
||||
static NSString *const kMachineIDPlistFileKey = @"MachineIDPlist";
|
||||
static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
|
||||
static NSString * const kSyncBaseURLKey = @"SyncBaseURL";
|
||||
static NSString * const kClientAuthCertificateFileKey = @"ClientAuthCertificateFile";
|
||||
static NSString * const kClientAuthCertificatePasswordKey = @"ClientAuthCertificatePassword";
|
||||
static NSString * const kClientAuthCertificateCNKey = @"ClientAuthCertificateCN";
|
||||
static NSString * const kClientAuthCertificateIssuerKey = @"ClientAuthCertificateIssuerCN";
|
||||
static NSString * const kServerAuthRootsDataKey = @"ServerAuthRootsData";
|
||||
static NSString * const kServerAuthRootsFileKey = @"ServerAuthRootsFile";
|
||||
static NSString *const kMoreInfoURLKey = @"MoreInfoURL";
|
||||
static NSString *const kEventDetailURLKey = @"EventDetailURL";
|
||||
static NSString *const kEventDetailTextKey = @"EventDetailText";
|
||||
static NSString *const kUnknownBlockMessage = @"UnknownBlockMessage";
|
||||
static NSString *const kBannedBlockMessage = @"BannedBlockMessage";
|
||||
static NSString *const kModeNotificationMonitor = @"ModeNotificationMonitor";
|
||||
static NSString *const kModeNotificationLockdown = @"ModeNotificationLockdown";
|
||||
|
||||
static NSString * const kMachineOwnerKey = @"MachineOwner";
|
||||
static NSString * const kMachineIDKey = @"MachineID";
|
||||
static NSString *const kEnablePageZeroProtectionKey = @"EnablePageZeroProtection";
|
||||
static NSString *const kEnableBadSignatureProtectionKey = @"EnableBadSignatureProtection";
|
||||
|
||||
static NSString * const kMachineOwnerPlistFileKey = @"MachineOwnerPlist";
|
||||
static NSString * const kMachineOwnerPlistKeyKey = @"MachineOwnerKey";
|
||||
static NSString *const kFileChangesRegexKey = @"FileChangesRegex";
|
||||
static NSString *const kFileChangesPrefixFiltersKey = @"FileChangesPrefixFilters";
|
||||
|
||||
static NSString * const kMachineIDPlistFileKey = @"MachineIDPlist";
|
||||
static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
static NSString *const kEventLogType = @"EventLogType";
|
||||
static NSString *const kEventLogPath = @"EventLogPath";
|
||||
|
||||
- (instancetype)initWithFilePath:(NSString *)filePath {
|
||||
static NSString *const kEnableMachineIDDecoration = @"EnableMachineIDDecoration";
|
||||
|
||||
static NSString *const kEnableSystemExtension = @"EnableSystemExtension";
|
||||
|
||||
// The keys managed by a sync server or mobileconfig.
|
||||
static NSString *const kClientModeKey = @"ClientMode";
|
||||
static NSString *const kEnableTransitiveWhitelistingKey = @"EnableTransitiveWhitelisting";
|
||||
static NSString *const kWhitelistRegexKey = @"WhitelistRegex";
|
||||
static NSString *const kBlacklistRegexKey = @"BlacklistRegex";
|
||||
|
||||
// The keys managed by a sync server.
|
||||
static NSString *const kFullSyncLastSuccess = @"FullSyncLastSuccess";
|
||||
static NSString *const kRuleSyncLastSuccess = @"RuleSyncLastSuccess";
|
||||
static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_configFilePath = filePath;
|
||||
[self reloadConfigData];
|
||||
Class number = [NSNumber class];
|
||||
Class re = [NSRegularExpression class];
|
||||
Class date = [NSDate class];
|
||||
Class string = [NSString class];
|
||||
Class data = [NSData class];
|
||||
Class array = [NSArray class];
|
||||
_syncServerKeyTypes = @{
|
||||
kClientModeKey : number,
|
||||
kEnableTransitiveWhitelistingKey : number,
|
||||
kWhitelistRegexKey : re,
|
||||
kBlacklistRegexKey : re,
|
||||
kFullSyncLastSuccess : date,
|
||||
kRuleSyncLastSuccess : date,
|
||||
kSyncCleanRequired : number
|
||||
};
|
||||
_forcedConfigKeyTypes = @{
|
||||
kClientModeKey : number,
|
||||
kEnableTransitiveWhitelistingKey : number,
|
||||
kFileChangesRegexKey : re,
|
||||
kFileChangesPrefixFiltersKey : array,
|
||||
kWhitelistRegexKey : re,
|
||||
kBlacklistRegexKey : re,
|
||||
kEnablePageZeroProtectionKey : number,
|
||||
kEnableBadSignatureProtectionKey: number,
|
||||
kMoreInfoURLKey : string,
|
||||
kEventDetailURLKey : string,
|
||||
kEventDetailTextKey : string,
|
||||
kUnknownBlockMessage : string,
|
||||
kBannedBlockMessage : string,
|
||||
kModeNotificationMonitor : string,
|
||||
kModeNotificationLockdown : string,
|
||||
kSyncBaseURLKey : string,
|
||||
kClientAuthCertificateFileKey : string,
|
||||
kClientAuthCertificatePasswordKey : string,
|
||||
kClientAuthCertificateCNKey : string,
|
||||
kClientAuthCertificateIssuerKey : string,
|
||||
kServerAuthRootsDataKey : data,
|
||||
kServerAuthRootsFileKey : string,
|
||||
kMachineOwnerKey : string,
|
||||
kMachineIDKey : string,
|
||||
kMachineOwnerPlistFileKey : string,
|
||||
kMachineOwnerPlistKeyKey : string,
|
||||
kMachineIDPlistFileKey : string,
|
||||
kMachineIDPlistKeyKey : string,
|
||||
kEventLogType : string,
|
||||
kEventLogPath : string,
|
||||
kEnableMachineIDDecoration : number,
|
||||
kEnableSystemExtension : number,
|
||||
};
|
||||
_defaults = [NSUserDefaults standardUserDefaults];
|
||||
[_defaults addSuiteNamed:@"com.google.santa"];
|
||||
_configState = [self readForcedConfig];
|
||||
_syncState = [self readSyncStateFromDisk] ?: [NSMutableDictionary dictionary];
|
||||
[self startWatchingDefaults];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -65,165 +153,483 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
#pragma mark Singleton retriever
|
||||
|
||||
+ (instancetype)configurator {
|
||||
static SNTConfigurator *sharedConfigurator = nil;
|
||||
static SNTConfigurator *sharedConfigurator;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedConfigurator = [[SNTConfigurator alloc] initWithFilePath:kDefaultConfigFilePath];
|
||||
sharedConfigurator = [[SNTConfigurator alloc] init];
|
||||
});
|
||||
return sharedConfigurator;
|
||||
}
|
||||
|
||||
+ (NSSet *)syncAndConfigStateSet {
|
||||
static NSSet *set;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
set = [[self syncStateSet] setByAddingObjectsFromSet:[self configStateSet]];
|
||||
});
|
||||
return set;
|
||||
}
|
||||
|
||||
+ (NSSet *)syncStateSet {
|
||||
static NSSet *set;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
set = [NSSet setWithObject:NSStringFromSelector(@selector(syncState))];
|
||||
});
|
||||
return set;
|
||||
}
|
||||
|
||||
+ (NSSet *)configStateSet {
|
||||
static NSSet *set;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
set = [NSSet setWithObject:NSStringFromSelector(@selector(configState))];
|
||||
});
|
||||
return set;
|
||||
}
|
||||
|
||||
#pragma mark KVO Dependencies
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingClientMode {
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingWhitelistPathRegex {
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingBlacklistPathRegex {
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingFileChangesRegex {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingFileChangesPrefixFiltersKey {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncBaseURL {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnablePageZeroProtection {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingMoreInfoURL {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEventDetailURL {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEventDetailText {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingUnknownBlockMessage {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingBannedBlockMessage {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingModeNotificationMonitor {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingModeNotificationLockdown {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncClientAuthCertificateFile {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncClientAuthCertificatePassword {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncClientAuthCertificateCn {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncClientAuthCertificateIssuer {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncServerAuthRootsData {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncServerAuthRootsFile {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingMachineOwner {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingMachineID {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingFullSyncLastSuccess {
|
||||
return [self syncStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingRuleSyncLastSuccess {
|
||||
return [self syncStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncCleanRequired {
|
||||
return [self syncStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEventLogType {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEventLogPath {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableMachineIDDecoration {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableTransitiveWhitelisting {
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableSystemExtension {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
#pragma mark Public Interface
|
||||
|
||||
- (santa_clientmode_t)clientMode {
|
||||
int cm = [self.configData[kClientModeKey] intValue];
|
||||
if (cm > CLIENTMODE_UNKNOWN && cm < CLIENTMODE_MAX) {
|
||||
return (santa_clientmode_t)cm;
|
||||
- (SNTClientMode)clientMode {
|
||||
SNTClientMode cm = [self.syncState[kClientModeKey] longLongValue];
|
||||
if (cm == SNTClientModeMonitor || cm == SNTClientModeLockdown) {
|
||||
return cm;
|
||||
}
|
||||
|
||||
cm = [self.configState[kClientModeKey] longLongValue];
|
||||
if (cm == SNTClientModeMonitor || cm == SNTClientModeLockdown) {
|
||||
return cm;
|
||||
}
|
||||
|
||||
return SNTClientModeMonitor;
|
||||
}
|
||||
|
||||
- (void)setSyncServerClientMode:(SNTClientMode)newMode {
|
||||
if (newMode == SNTClientModeMonitor || newMode == SNTClientModeLockdown) {
|
||||
[self updateSyncStateForKey:kClientModeKey value:@(newMode)];
|
||||
} else {
|
||||
self.configData[kClientModeKey] = @(CLIENTMODE_MONITOR);
|
||||
return CLIENTMODE_MONITOR;
|
||||
LOGW(@"Ignoring request to change client mode to %ld", newMode);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setClientMode:(santa_clientmode_t)newMode {
|
||||
if (newMode > CLIENTMODE_UNKNOWN && newMode < CLIENTMODE_MAX) {
|
||||
self.configData[kClientModeKey] = @(newMode);
|
||||
[self saveConfigToDisk];
|
||||
- (BOOL)enableTransitiveWhitelisting {
|
||||
NSNumber *n = self.syncState[kEnableTransitiveWhitelistingKey];
|
||||
if (n) {
|
||||
return [n boolValue];
|
||||
}
|
||||
return [self.configState[kEnableTransitiveWhitelistingKey] boolValue];
|
||||
}
|
||||
|
||||
- (BOOL)logAllEvents {
|
||||
return [self.configData[kLogAllEventsKey] boolValue];
|
||||
- (void)setEnableTransitiveWhitelisting:(BOOL)enabled {
|
||||
[self updateSyncStateForKey:kEnableTransitiveWhitelistingKey value:@(enabled)];
|
||||
}
|
||||
|
||||
- (void)setLogAllEvents:(BOOL)logAllEvents {
|
||||
self.configData[kLogAllEventsKey] = @(logAllEvents);
|
||||
[self saveConfigToDisk];
|
||||
- (NSRegularExpression *)whitelistPathRegex {
|
||||
return self.syncState[kWhitelistRegexKey] ?: self.configState[kWhitelistRegexKey];
|
||||
}
|
||||
|
||||
- (NSURL *)moreInfoURL {
|
||||
return [NSURL URLWithString:self.configData[kMoreInfoURLKey]];
|
||||
- (void)setSyncServerWhitelistPathRegex:(NSRegularExpression *)re {
|
||||
[self updateSyncStateForKey:kWhitelistRegexKey value:re];
|
||||
}
|
||||
|
||||
- (NSString *)eventDetailURL {
|
||||
return self.configData[kEventDetailURLKey];
|
||||
- (NSRegularExpression *)blacklistPathRegex {
|
||||
return self.syncState[kBlacklistRegexKey] ?: self.configState[kBlacklistRegexKey];
|
||||
}
|
||||
|
||||
- (NSString *)eventDetailText {
|
||||
return self.configData[kEventDetailTextKey];
|
||||
- (void)setSyncServerBlacklistPathRegex:(NSRegularExpression *)re {
|
||||
[self updateSyncStateForKey:kBlacklistRegexKey value:re];
|
||||
}
|
||||
|
||||
- (NSRegularExpression *)fileChangesRegex {
|
||||
return self.configState[kFileChangesRegexKey];
|
||||
}
|
||||
|
||||
- (NSArray *)fileChangesPrefixFilters {
|
||||
NSArray *filters = self.configState[kFileChangesPrefixFiltersKey];
|
||||
for (id filter in filters) {
|
||||
if (![filter isKindOfClass:[NSString class]]) {
|
||||
LOGE(@"Ignoring FileChangesPrefixFilters: array contains a non-string %@", filter);
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
return filters;
|
||||
}
|
||||
|
||||
- (NSURL *)syncBaseURL {
|
||||
return [NSURL URLWithString:self.configData[kSyncBaseURLKey]];
|
||||
NSString *urlString = self.configState[kSyncBaseURLKey];
|
||||
if (![urlString hasSuffix:@"/"]) urlString = [urlString stringByAppendingString:@"/"];
|
||||
NSURL *url = [NSURL URLWithString:urlString];
|
||||
if (urlString && !url) LOGW(@"SyncBaseURL is not a valid URL!");
|
||||
return url;
|
||||
}
|
||||
|
||||
- (BOOL)enablePageZeroProtection {
|
||||
NSNumber *number = self.configState[kEnablePageZeroProtectionKey];
|
||||
return number ? [number boolValue] : YES;
|
||||
}
|
||||
|
||||
- (BOOL)enableBadSignatureProtection {
|
||||
NSNumber *number = self.configState[kEnableBadSignatureProtectionKey];
|
||||
return number ? [number boolValue] : NO;
|
||||
}
|
||||
|
||||
- (NSURL *)moreInfoURL {
|
||||
return [NSURL URLWithString:self.configState[kMoreInfoURLKey]];
|
||||
}
|
||||
|
||||
- (NSString *)eventDetailURL {
|
||||
return self.configState[kEventDetailURLKey];
|
||||
}
|
||||
|
||||
- (NSString *)eventDetailText {
|
||||
return self.configState[kEventDetailTextKey];
|
||||
}
|
||||
|
||||
- (NSString *)unknownBlockMessage {
|
||||
return self.configState[kUnknownBlockMessage];
|
||||
}
|
||||
|
||||
- (NSString *)bannedBlockMessage {
|
||||
return self.configState[kBannedBlockMessage];
|
||||
}
|
||||
|
||||
- (NSString *)modeNotificationMonitor {
|
||||
return self.configState[kModeNotificationMonitor];
|
||||
}
|
||||
|
||||
- (NSString *)modeNotificationLockdown {
|
||||
return self.configState[kModeNotificationLockdown];
|
||||
}
|
||||
|
||||
- (NSString *)syncClientAuthCertificateFile {
|
||||
return self.configData[kClientAuthCertificateFileKey];
|
||||
return self.configState[kClientAuthCertificateFileKey];
|
||||
}
|
||||
|
||||
- (NSString *)syncClientAuthCertificatePassword {
|
||||
return self.configData[kClientAuthCertificatePasswordKey];
|
||||
return self.configState[kClientAuthCertificatePasswordKey];
|
||||
}
|
||||
|
||||
- (NSString *)syncClientAuthCertificateCn {
|
||||
return self.configData[kClientAuthCertificateCNKey];
|
||||
return self.configState[kClientAuthCertificateCNKey];
|
||||
}
|
||||
|
||||
- (NSString *)syncClientAuthCertificateIssuer {
|
||||
return self.configData[kClientAuthCertificateIssuerKey];
|
||||
return self.configState[kClientAuthCertificateIssuerKey];
|
||||
}
|
||||
|
||||
- (NSData *)syncServerAuthRootsData {
|
||||
return self.configData[kServerAuthRootsDataKey];
|
||||
return self.configState[kServerAuthRootsDataKey];
|
||||
}
|
||||
|
||||
- (NSString *)syncServerAuthRootsFile {
|
||||
return self.configData[kServerAuthRootsFileKey];
|
||||
return self.configState[kServerAuthRootsFileKey];
|
||||
}
|
||||
|
||||
- (NSDate *)fullSyncLastSuccess {
|
||||
return self.syncState[kFullSyncLastSuccess];
|
||||
}
|
||||
|
||||
- (void)setFullSyncLastSuccess:(NSDate *)fullSyncLastSuccess {
|
||||
[self updateSyncStateForKey:kFullSyncLastSuccess value:fullSyncLastSuccess];
|
||||
self.ruleSyncLastSuccess = fullSyncLastSuccess;
|
||||
}
|
||||
|
||||
- (NSDate *)ruleSyncLastSuccess {
|
||||
return self.syncState[kRuleSyncLastSuccess];
|
||||
}
|
||||
|
||||
- (void)setRuleSyncLastSuccess:(NSDate *)ruleSyncLastSuccess {
|
||||
[self updateSyncStateForKey:kRuleSyncLastSuccess value:ruleSyncLastSuccess];
|
||||
}
|
||||
|
||||
- (BOOL)syncCleanRequired {
|
||||
return [self.syncState[kSyncCleanRequired] boolValue];
|
||||
}
|
||||
|
||||
- (void)setSyncCleanRequired:(BOOL)syncCleanRequired {
|
||||
[self updateSyncStateForKey:kSyncCleanRequired value:@(syncCleanRequired)];
|
||||
}
|
||||
|
||||
- (NSString *)machineOwner {
|
||||
NSString *machineOwner;
|
||||
NSString *machineOwner = self.configState[kMachineOwnerKey];
|
||||
if (machineOwner) return machineOwner;
|
||||
|
||||
if (self.configData[kMachineOwnerPlistFileKey] && self.configData[kMachineOwnerPlistKeyKey]) {
|
||||
NSDictionary *plist =
|
||||
[NSDictionary dictionaryWithContentsOfFile:self.configData[kMachineOwnerPlistFileKey]];
|
||||
machineOwner = plist[self.configData[kMachineOwnerPlistKeyKey]];
|
||||
NSString *plistPath = self.configState[kMachineOwnerPlistFileKey];
|
||||
NSString *plistKey = self.configState[kMachineOwnerPlistKeyKey];
|
||||
if (plistPath && plistKey) {
|
||||
NSDictionary *plist = [NSDictionary dictionaryWithContentsOfFile:plistPath];
|
||||
machineOwner = [plist[plistKey] isKindOfClass:[NSString class]] ? plist[plistKey] : nil;
|
||||
}
|
||||
|
||||
if (self.configData[kMachineOwnerKey]) {
|
||||
machineOwner = self.configData[kMachineOwnerKey];
|
||||
}
|
||||
|
||||
if (!machineOwner) machineOwner = @"";
|
||||
|
||||
return machineOwner;
|
||||
return machineOwner ?: @"";
|
||||
}
|
||||
|
||||
- (NSString *)machineID {
|
||||
NSString *machineId;
|
||||
NSString *machineId = self.configState[kMachineIDKey];
|
||||
if (machineId) return machineId;
|
||||
|
||||
if (self.configData[kMachineIDPlistFileKey] && self.configData[kMachineIDPlistKeyKey]) {
|
||||
NSDictionary *plist =
|
||||
[NSDictionary dictionaryWithContentsOfFile:self.configData[kMachineIDPlistFileKey]];
|
||||
machineId = plist[self.configData[kMachineIDPlistKeyKey]];
|
||||
NSString *plistPath = self.configState[kMachineIDPlistFileKey];
|
||||
NSString *plistKey = self.configState[kMachineIDPlistKeyKey];
|
||||
|
||||
if (plistPath && plistKey) {
|
||||
NSDictionary *plist = [NSDictionary dictionaryWithContentsOfFile:plistPath];
|
||||
machineId = [plist[plistKey] isKindOfClass:[NSString class]] ? plist[plistKey] : nil;
|
||||
}
|
||||
|
||||
if (self.configData[kMachineIDKey]) {
|
||||
machineId = self.configData[kMachineIDKey];
|
||||
}
|
||||
|
||||
if ([machineId length] == 0) {
|
||||
machineId = [SNTSystemInfo hardwareUUID];
|
||||
}
|
||||
|
||||
return machineId;
|
||||
return machineId.length ? machineId : [SNTSystemInfo hardwareUUID];
|
||||
}
|
||||
|
||||
- (void)reloadConfigData {
|
||||
if (!self.configData) self.configData = [NSMutableDictionary dictionary];
|
||||
- (SNTEventLogType)eventLogType {
|
||||
NSString *s = [self.configState[kEventLogType] lowercaseString];
|
||||
return [s isEqualToString:@"syslog"] ? SNTEventLogTypeSyslog : SNTEventLogTypeFilelog;
|
||||
}
|
||||
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
if (![fm fileExistsAtPath:self.configFilePath]) return;
|
||||
- (NSString *)eventLogPath {
|
||||
return self.configState[kEventLogPath] ?: @"/var/db/santa/santa.log";
|
||||
}
|
||||
|
||||
NSError *error;
|
||||
NSData *readData = [NSData dataWithContentsOfFile:self.configFilePath
|
||||
options:NSDataReadingMappedIfSafe
|
||||
error:&error];
|
||||
if (error) {
|
||||
LOGE(@"Could not read configuration file: %@", [error localizedDescription]);
|
||||
return;
|
||||
}
|
||||
- (BOOL)enableMachineIDDecoration {
|
||||
NSNumber *number = self.configState[kEnableMachineIDDecoration];
|
||||
return number ? [number boolValue] : NO;
|
||||
}
|
||||
|
||||
NSDictionary *configData =
|
||||
[NSPropertyListSerialization propertyListWithData:readData
|
||||
options:kCFPropertyListImmutable
|
||||
format:NULL
|
||||
error:&error];
|
||||
if (error) {
|
||||
LOGE(@"Could not parse configuration file: %@", [error localizedDescription]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure no-one is trying to change the client mode behind Santa's back.
|
||||
if (self.configData[kClientModeKey] && configData[kClientModeKey] &&
|
||||
![self.configData[kClientModeKey] isEqual:configData[kClientModeKey]] &&
|
||||
geteuid() == 0) {
|
||||
NSMutableDictionary *configDataMutable = [configData mutableCopy];
|
||||
configDataMutable[kClientModeKey] = self.configData[kClientModeKey];
|
||||
self.configData = configDataMutable;
|
||||
[self saveConfigToDisk];
|
||||
- (BOOL)enableSystemExtension {
|
||||
if (@available(macOS 10.15, *)) {
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
if (![fm fileExistsAtPath:@"/Library/Extensions/santa-driver.kext"]) return YES;
|
||||
NSNumber *number = self.configState[kEnableSystemExtension];
|
||||
return number ? [number boolValue] : YES;
|
||||
} else {
|
||||
self.configData = [configData mutableCopy];
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark Private
|
||||
|
||||
///
|
||||
/// Saves the current @c self.configData to disk.
|
||||
/// Update the syncState. Triggers a KVO event for all dependents.
|
||||
///
|
||||
- (void)saveConfigToDisk {
|
||||
[self.configData writeToFile:self.configFilePath atomically:YES];
|
||||
- (void)updateSyncStateForKey:(NSString *)key value:(id)value {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSMutableDictionary *syncState = self.syncState.mutableCopy;
|
||||
syncState[key] = value;
|
||||
self.syncState = syncState;
|
||||
[self saveSyncStateToDisk];
|
||||
});
|
||||
}
|
||||
|
||||
///
|
||||
/// Read the saved syncState.
|
||||
///
|
||||
- (NSMutableDictionary *)readSyncStateFromDisk {
|
||||
// Only read the sync state if a sync server is configured.
|
||||
if (!self.syncBaseURL) return nil;
|
||||
// Only santad should read this file.
|
||||
if (geteuid() != 0) return nil;
|
||||
NSMutableDictionary *syncState =
|
||||
[NSMutableDictionary dictionaryWithContentsOfFile:kSyncStateFilePath];
|
||||
for (NSString *key in syncState.allKeys) {
|
||||
if (self.syncServerKeyTypes[key] == [NSRegularExpression class]) {
|
||||
NSString *pattern = [syncState[key] isKindOfClass:[NSString class]] ? syncState[key] : nil;
|
||||
syncState[key] = [self expressionForPattern:pattern];
|
||||
} else if (![syncState[key] isKindOfClass:self.syncServerKeyTypes[key]]) {
|
||||
syncState[key] = nil;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return syncState;
|
||||
}
|
||||
|
||||
///
|
||||
/// Saves the current effective syncState to disk.
|
||||
///
|
||||
- (void)saveSyncStateToDisk {
|
||||
// Only save the sync state if a sync server is configured.
|
||||
if (!self.syncBaseURL) return;
|
||||
// Only santad should write to this file.
|
||||
if (geteuid() != 0) return;
|
||||
// Either remove
|
||||
NSMutableDictionary *syncState = self.syncState.mutableCopy;
|
||||
syncState[kWhitelistRegexKey] = [syncState[kWhitelistRegexKey] pattern];
|
||||
syncState[kBlacklistRegexKey] = [syncState[kBlacklistRegexKey] pattern];
|
||||
[syncState writeToFile:kSyncStateFilePath atomically:YES];
|
||||
[[NSFileManager defaultManager] setAttributes:@{ NSFilePosixPermissions : @0644 }
|
||||
ofItemAtPath:kSyncStateFilePath error:NULL];
|
||||
}
|
||||
|
||||
- (void)clearSyncState {
|
||||
self.syncState = [NSMutableDictionary dictionary];
|
||||
}
|
||||
|
||||
#pragma mark Private Defaults Methods
|
||||
|
||||
- (NSRegularExpression *)expressionForPattern:(NSString *)pattern {
|
||||
if (!pattern) return nil;
|
||||
if (![pattern hasPrefix:@"^"]) pattern = [@"^" stringByAppendingString:pattern];
|
||||
return [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:NULL];
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *)readForcedConfig {
|
||||
NSMutableDictionary *forcedConfig = [NSMutableDictionary dictionary];
|
||||
for (NSString *key in self.forcedConfigKeyTypes) {
|
||||
id obj = [self forcedConfigValueForKey:key];
|
||||
forcedConfig[key] = [obj isKindOfClass:self.forcedConfigKeyTypes[key]] ? obj : nil;
|
||||
// Create the regex objects now
|
||||
if (self.forcedConfigKeyTypes[key] == [NSRegularExpression class]) {
|
||||
NSString *pattern = [obj isKindOfClass:[NSString class]] ? obj : nil;
|
||||
forcedConfig[key] = [self expressionForPattern:pattern];
|
||||
}
|
||||
}
|
||||
return forcedConfig;
|
||||
}
|
||||
|
||||
- (id)forcedConfigValueForKey:(NSString *)key {
|
||||
id obj = [self.defaults objectForKey:key];
|
||||
return [self.defaults objectIsForcedForKey:key inDomain:kMobileConfigDomain] ? obj : nil;
|
||||
}
|
||||
|
||||
- (void)startWatchingDefaults {
|
||||
// Only santad should listen.
|
||||
if (geteuid() != 0) return;
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(defaultsChanged:)
|
||||
name:NSUserDefaultsDidChangeNotification
|
||||
object:nil];
|
||||
}
|
||||
|
||||
- (void)defaultsChanged:(void *)v {
|
||||
SEL handleChange = @selector(handleChange);
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:handleChange object:nil];
|
||||
[self performSelector:handleChange withObject:nil afterDelay:5.0f];
|
||||
}
|
||||
|
||||
///
|
||||
/// Update the configState. Triggers a KVO event for all dependents.
|
||||
///
|
||||
- (void)handleChange {
|
||||
self.configState = [self readForcedConfig];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
///
|
||||
/// Simple function to check and drop root privileges.
|
||||
///
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTDropRootPrivs.h"
|
||||
#import "Source/common/SNTDropRootPrivs.h"
|
||||
|
||||
BOOL DropRootPrivileges() {
|
||||
if (getuid() == 0 || geteuid() == 0 || getgid() == 0 || getegid() == 0) {
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class MOLCodesignChecker;
|
||||
|
||||
///
|
||||
/// Represents a binary on disk, providing access to details about that binary
|
||||
/// such as the SHA-1, SHA-256, Info.plist and the Mach-O data.
|
||||
@@ -23,14 +27,44 @@
|
||||
///
|
||||
/// @param path The path of the file this instance is to represent. The path will be
|
||||
/// converted to an absolute, standardized path if it isn't already.
|
||||
/// @param error If an error occurred and nil is returned, this will be a pointer to an NSError
|
||||
/// describing the problem.
|
||||
///
|
||||
- (instancetype)initWithPath:(NSString *)path error:(NSError **)error;
|
||||
|
||||
///
|
||||
/// Convenience initializer.
|
||||
///
|
||||
/// @param path The path to the file this instance is to represent. The path will be
|
||||
/// converted to an absolute, standardized path if it isn't already.
|
||||
///
|
||||
- (instancetype)initWithPath:(NSString *)path;
|
||||
|
||||
|
||||
///
|
||||
/// Initializer for already resolved paths.
|
||||
///
|
||||
/// @param path The path of the file this instance is to represent. The path will
|
||||
/// not be converted and will be used as is. If the path is not a regular file this method will
|
||||
/// return nil and fill in an error.
|
||||
/// @param error If an error occurred and nil is returned, this will be a pointer to an NSError
|
||||
/// describing the problem.
|
||||
///
|
||||
- (instancetype)initWithResolvedPath:(NSString *)path error:(NSError **)error;
|
||||
|
||||
///
|
||||
/// @return Path of this file.
|
||||
///
|
||||
- (NSString *)path;
|
||||
|
||||
///
|
||||
/// Hash this file with SHA-1 and SHA-256 simultaneously.
|
||||
///
|
||||
/// @param sha1 If not NULL, will be filled with the SHA-1 of the file.
|
||||
/// @param sha256 If not NULL, will be filled with the SHA-256 of the file.
|
||||
///
|
||||
- (void)hashSHA1:(NSString **)sha1 SHA256:(NSString **)sha256;
|
||||
|
||||
///
|
||||
/// @return SHA-1 hash of this binary.
|
||||
///
|
||||
@@ -41,12 +75,6 @@
|
||||
///
|
||||
- (NSString *)SHA256;
|
||||
|
||||
///
|
||||
/// @return The type of Mach-O file, one of:
|
||||
/// Dynamic Library, Kernel Extension, Fat Binary or Thin Binary.
|
||||
///
|
||||
- (NSString *)machoType;
|
||||
|
||||
///
|
||||
/// @return The architectures included in this binary (e.g. x86_64, ppc).
|
||||
///
|
||||
@@ -72,6 +100,11 @@
|
||||
///
|
||||
- (BOOL)isDylib;
|
||||
|
||||
///
|
||||
/// @return YES if this file is a bundle executable (QuickLook/Spotlight plugin, etc.)
|
||||
///
|
||||
- (BOOL)isBundle;
|
||||
|
||||
///
|
||||
/// @return YES if this file is a kernel extension.
|
||||
///
|
||||
@@ -82,6 +115,41 @@
|
||||
///
|
||||
- (BOOL)isScript;
|
||||
|
||||
///
|
||||
/// @return YES if this file is an XAR archive.
|
||||
///
|
||||
- (BOOL)isXARArchive;
|
||||
|
||||
///
|
||||
/// @return YES if this file is a disk image.
|
||||
///
|
||||
- (BOOL)isDMG;
|
||||
|
||||
///
|
||||
/// @return NSString describing the kind of file (executable, bundle, script, etc.)
|
||||
///
|
||||
- (NSString *)humanReadableFileType;
|
||||
|
||||
///
|
||||
/// @return YES if this file has a bad/missing __PAGEZERO .
|
||||
///
|
||||
- (BOOL)isMissingPageZero;
|
||||
|
||||
///
|
||||
/// If set to YES, the bundle* and infoPlist methods will search for and use the highest NSBundle
|
||||
/// found in the tree. Defaults to NO, which uses the first found bundle, if any.
|
||||
///
|
||||
/// @example:
|
||||
/// An SNTFileInfo object that represents
|
||||
/// /Applications/Photos.app/Contents/XPCServices/com.apple.Photos.librarychooserservice.xpc
|
||||
/// useAncestorBundle is set to YES
|
||||
/// /Applications/Photos.app will be used to get data backing all the bundle methods
|
||||
///
|
||||
/// @note: The NSBundle object backing the bundle* and infoPlist methods is cached once found.
|
||||
/// Setting the useAncestorBundle propery will clear this cache and force a re-search.
|
||||
///
|
||||
@property(nonatomic) BOOL useAncestorBundle;
|
||||
|
||||
///
|
||||
/// @return An NSBundle if this file is part of a bundle.
|
||||
///
|
||||
@@ -94,8 +162,8 @@
|
||||
|
||||
///
|
||||
/// @return Either the Info.plist in the bundle this file is part of, or an embedded plist if there
|
||||
/// is one. In the odd case that a file has both an embedded Info.plist and is part of a bundle,
|
||||
/// the Info.plist from the bundle will be returned.
|
||||
/// is one. In the unlikely event that a file has both an embedded Info.plist and is part of a
|
||||
/// bundle, the embedded plist will be returned.
|
||||
///
|
||||
- (NSDictionary *)infoPlist;
|
||||
|
||||
@@ -120,9 +188,40 @@
|
||||
- (NSString *)bundleShortVersionString;
|
||||
|
||||
///
|
||||
/// @return any URLs this file may have been downloaded from, using the
|
||||
/// @c com.apple.metadata:kMDItemWhereFroms extended attribute.
|
||||
/// @return LaunchServices quarantine data - download URL as an absolute string.
|
||||
///
|
||||
- (NSArray *)downloadURLs;
|
||||
- (NSString *)quarantineDataURL;
|
||||
|
||||
///
|
||||
/// @return LaunchServices quarantine data - referer URL as an absolute string.
|
||||
///
|
||||
- (NSString *)quarantineRefererURL;
|
||||
|
||||
///
|
||||
/// @return LaunchServices quarantine data - agent bundle ID.
|
||||
///
|
||||
- (NSString *)quarantineAgentBundleID;
|
||||
|
||||
///
|
||||
/// @return LaunchServices quarantine data - timestamp.
|
||||
///
|
||||
- (NSDate *)quarantineTimestamp;
|
||||
|
||||
///
|
||||
/// @return The size of the file in bytes.
|
||||
///
|
||||
- (NSUInteger)fileSize;
|
||||
|
||||
///
|
||||
/// @return The underlying file handle.
|
||||
///
|
||||
@property(readonly) NSFileHandle *fileHandle;
|
||||
|
||||
///
|
||||
/// @return Returns an instance of MOLCodeSignChecker initialized with the file's binary path.
|
||||
/// Both the MOLCodesignChecker and any resulting NSError are cached and returned on subsequent
|
||||
/// calls. You may pass in NULL for the error if you don't care to receive it.
|
||||
///
|
||||
- (MOLCodesignChecker *)codesignCheckerWithError:(NSError **)error;
|
||||
|
||||
@end
|
||||
|
||||
@@ -12,346 +12,725 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTFileInfo.h"
|
||||
#import "Source/common/SNTFileInfo.h"
|
||||
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
#import <fmdb/FMDB.h>
|
||||
#import <MOLCodesignChecker/MOLCodesignChecker.h>
|
||||
|
||||
#include <mach-o/arch.h>
|
||||
#include <mach-o/loader.h>
|
||||
#include <mach-o/swap.h>
|
||||
#include <pwd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/xattr.h>
|
||||
|
||||
|
||||
// Simple class to hold the data of a mach_header and the offset within the file
|
||||
// in which that header was found.
|
||||
@interface MachHeaderWithOffset : NSObject
|
||||
@property NSData *data;
|
||||
@property uint32_t offset;
|
||||
- (instancetype)initWithData:(NSData *)data offset:(uint32_t)offset;
|
||||
@end
|
||||
@implementation MachHeaderWithOffset
|
||||
- (instancetype)initWithData:(NSData *)data offset:(uint32_t)offset {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_data = data;
|
||||
_offset = offset;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@end
|
||||
|
||||
@interface SNTFileInfo ()
|
||||
@property NSString *path;
|
||||
@property NSData *fileData;
|
||||
@property NSFileHandle *fileHandle;
|
||||
@property NSUInteger fileSize;
|
||||
@property NSString *fileOwnerHomeDir;
|
||||
|
||||
// Cached properties
|
||||
@property NSBundle *bundleRef;
|
||||
@property NSDictionary *infoDict;
|
||||
@property NSDictionary *quarantineDict;
|
||||
@property NSDictionary *cachedHeaders;
|
||||
@property MOLCodesignChecker *cachedCodesignChecker;
|
||||
@property(nonatomic) NSError *codesignCheckerError;
|
||||
@end
|
||||
|
||||
@implementation SNTFileInfo
|
||||
|
||||
- (instancetype)initWithPath:(NSString *)path {
|
||||
extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
|
||||
- (instancetype)initWithResolvedPath:(NSString *)path error:(NSError **)error {
|
||||
self = [super init];
|
||||
|
||||
if (self) {
|
||||
// Convert to absolute, standardized path
|
||||
path = [path stringByResolvingSymlinksInPath];
|
||||
if (![path isAbsolutePath]) {
|
||||
NSString *cwd = [[NSFileManager defaultManager] currentDirectoryPath];
|
||||
path = [cwd stringByAppendingPathComponent:path];
|
||||
}
|
||||
path = [path stringByStandardizingPath];
|
||||
|
||||
// Determine if file exists.
|
||||
// If path is actually a directory, check to see if it's a bundle and has a CFBundleExecutable.
|
||||
BOOL directory;
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&directory]) {
|
||||
return nil;
|
||||
} else if (directory) {
|
||||
NSString *infoPath = [path stringByAppendingPathComponent:@"Contents/Info.plist"];
|
||||
NSDictionary *d = [NSDictionary dictionaryWithContentsOfFile:infoPath];
|
||||
if (d && d[@"CFBundleExecutable"]) {
|
||||
path = [path stringByAppendingPathComponent:@"Contents/MacOS"];
|
||||
_path = [path stringByAppendingPathComponent:d[@"CFBundleExecutable"]];
|
||||
} else {
|
||||
return nil;
|
||||
_path = path;
|
||||
if (!_path.length) {
|
||||
if (error) {
|
||||
NSString *errStr = @"Unable to use empty path";
|
||||
*error = [NSError errorWithDomain:@"com.google.santa.fileinfo"
|
||||
code:270
|
||||
userInfo:@{NSLocalizedDescriptionKey : errStr}];
|
||||
}
|
||||
} else {
|
||||
_path = path;
|
||||
return nil;
|
||||
}
|
||||
|
||||
_fileData = [NSData dataWithContentsOfFile:_path options:NSDataReadingMappedIfSafe error:nil];
|
||||
if (!_fileData) return nil;
|
||||
struct stat fileStat;
|
||||
lstat(_path.UTF8String, &fileStat);
|
||||
if (!((S_IFMT & fileStat.st_mode) == S_IFREG)) {
|
||||
if (error) {
|
||||
NSString *errStr = [NSString stringWithFormat:@"Non regular file: %s", strerror(errno)];
|
||||
*error = [NSError errorWithDomain:@"com.google.santa.fileinfo"
|
||||
code:290
|
||||
userInfo:@{NSLocalizedDescriptionKey : errStr}];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
_fileSize = fileStat.st_size;
|
||||
|
||||
if (_fileSize == 0) return nil;
|
||||
|
||||
if (fileStat.st_uid != 0) {
|
||||
struct passwd *pwd = getpwuid(fileStat.st_uid);
|
||||
if (pwd) {
|
||||
_fileOwnerHomeDir = @(pwd->pw_dir);
|
||||
}
|
||||
}
|
||||
|
||||
int fd = open([_path UTF8String], O_RDONLY | O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
if (error) {
|
||||
NSString *errStr = [NSString stringWithFormat:@"Unable to open file: %s", strerror(errno)];
|
||||
*error = [NSError errorWithDomain:@"com.google.santa.fileinfo"
|
||||
code:280
|
||||
userInfo:@{NSLocalizedDescriptionKey : errStr}];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
_fileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:YES];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)SHA1 {
|
||||
unsigned char sha1[CC_SHA1_DIGEST_LENGTH];
|
||||
CC_SHA1([self.fileData bytes], (unsigned int)[self.fileData length], sha1);
|
||||
|
||||
// Convert the binary SHA into hex
|
||||
NSMutableString *buf = [[NSMutableString alloc] initWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
|
||||
for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) {
|
||||
[buf appendFormat:@"%02x", (unsigned char)sha1[i]];
|
||||
- (instancetype)initWithPath:(NSString *)path error:(NSError **)error {
|
||||
NSBundle *bndl;
|
||||
NSString *resolvedPath = [self resolvePath:path bundle:&bndl];
|
||||
if (!resolvedPath.length) {
|
||||
if (error) {
|
||||
NSString *errStr = @"Unable to resolve empty path";
|
||||
if (path) errStr = [@"Unable to resolve path: " stringByAppendingString:path];
|
||||
*error = [NSError errorWithDomain:@"com.google.santa.fileinfo"
|
||||
code:260
|
||||
userInfo:@{NSLocalizedDescriptionKey : errStr}];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
self = [self initWithResolvedPath:resolvedPath error:error];
|
||||
if (self && bndl) _bundleRef = bndl;
|
||||
return self;
|
||||
}
|
||||
|
||||
return buf;
|
||||
- (instancetype)initWithPath:(NSString *)path {
|
||||
return [self initWithPath:path error:NULL];
|
||||
}
|
||||
|
||||
#pragma mark Hashing
|
||||
|
||||
- (void)hashSHA1:(NSString **)sha1 SHA256:(NSString **)sha256 {
|
||||
const int MAX_CHUNK_SIZE = 256 * 1024; // 256 KB
|
||||
const size_t chunkSize = _fileSize > MAX_CHUNK_SIZE ? MAX_CHUNK_SIZE : _fileSize;
|
||||
char *chunk = malloc(chunkSize);
|
||||
|
||||
@try {
|
||||
CC_SHA1_CTX c1;
|
||||
CC_SHA256_CTX c256;
|
||||
|
||||
if (sha1) CC_SHA1_Init(&c1);
|
||||
if (sha256) CC_SHA256_Init(&c256);
|
||||
|
||||
int fd = self.fileHandle.fileDescriptor;
|
||||
|
||||
fcntl(fd, F_RDAHEAD, 1);
|
||||
struct radvisory radv;
|
||||
radv.ra_offset = 0;
|
||||
const int MAX_ADVISORY_READ = 10 * 1024 * 1024;
|
||||
radv.ra_count = (int)_fileSize < MAX_ADVISORY_READ ? (int)_fileSize : MAX_ADVISORY_READ;
|
||||
fcntl(fd, F_RDADVISE, &radv);
|
||||
ssize_t bytesRead;
|
||||
|
||||
for (uint64_t offset = 0; offset < _fileSize;) {
|
||||
bytesRead = pread(fd, chunk, chunkSize, offset);
|
||||
if (bytesRead > 0) {
|
||||
if (sha1) CC_SHA1_Update(&c1, chunk, (CC_LONG)bytesRead);
|
||||
if (sha256) CC_SHA256_Update(&c256, chunk, (CC_LONG)bytesRead);
|
||||
offset += bytesRead;
|
||||
} else if (bytesRead == -1 && errno == EINTR) {
|
||||
continue;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// We turn off Read Ahead that we turned on
|
||||
fcntl(fd, F_RDAHEAD, 0);
|
||||
if (sha1) {
|
||||
unsigned char digest[CC_SHA1_DIGEST_LENGTH];
|
||||
CC_SHA1_Final(digest, &c1);
|
||||
NSString *const SHA1FormatString =
|
||||
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";
|
||||
*sha1 = [[NSString alloc]
|
||||
initWithFormat:SHA1FormatString, digest[0], digest[1], digest[2],
|
||||
digest[3], digest[4], digest[5], digest[6], digest[7],
|
||||
digest[8], digest[9], digest[10], digest[11], digest[12],
|
||||
digest[13], digest[14], digest[15], digest[16],
|
||||
digest[17], digest[18], digest[19]];
|
||||
}
|
||||
if (sha256) {
|
||||
unsigned char digest[CC_SHA256_DIGEST_LENGTH];
|
||||
CC_SHA256_Final(digest, &c256);
|
||||
NSString *const SHA256FormatString =
|
||||
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
|
||||
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";
|
||||
|
||||
*sha256 = [[NSString alloc]
|
||||
initWithFormat:SHA256FormatString, digest[0], digest[1], digest[2],
|
||||
digest[3], digest[4], digest[5], digest[6], digest[7],
|
||||
digest[8], digest[9], digest[10], digest[11], digest[12],
|
||||
digest[13], digest[14], digest[15], digest[16],
|
||||
digest[17], digest[18], digest[19], digest[20],
|
||||
digest[21], digest[22], digest[23], digest[24],
|
||||
digest[25], digest[26], digest[27], digest[28],
|
||||
digest[29], digest[30], digest[31]];
|
||||
}
|
||||
} @finally {
|
||||
free(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)SHA1 {
|
||||
NSString *sha1;
|
||||
[self hashSHA1:&sha1 SHA256:NULL];
|
||||
return sha1;
|
||||
}
|
||||
|
||||
- (NSString *)SHA256 {
|
||||
unsigned char sha2[CC_SHA256_DIGEST_LENGTH];
|
||||
CC_SHA256(self.fileData.bytes, (unsigned int)self.fileData.length, sha2);
|
||||
|
||||
NSMutableString *buf = [[NSMutableString alloc] initWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
|
||||
for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {
|
||||
[buf appendFormat:@"%02x", (unsigned char)sha2[i]];
|
||||
}
|
||||
|
||||
return buf;
|
||||
NSString *sha256;
|
||||
[self hashSHA1:NULL SHA256:&sha256];
|
||||
return sha256;
|
||||
}
|
||||
|
||||
- (NSString *)machoType {
|
||||
if ([self isDylib]) return @"Dynamic Library";
|
||||
if ([self isKext]) return @"Kernel Extension";
|
||||
if ([self isFat]) return @"Fat Binary";
|
||||
if ([self isMachO]) return @"Thin Binary";
|
||||
if ([self isScript]) return @"Script";
|
||||
return @"Unknown (not executable?)";
|
||||
}
|
||||
#pragma mark File Type Info
|
||||
|
||||
- (NSArray *)architectures {
|
||||
if (![self isMachO]) return nil;
|
||||
|
||||
if ([self isFat]) {
|
||||
NSMutableArray *ret = [[NSMutableArray alloc] init];
|
||||
|
||||
// Retrieve just the fat_header, if possible.
|
||||
NSData *head = [self safeSubdataWithRange:NSMakeRange(0, sizeof(struct fat_header))];
|
||||
if (!head) return nil;
|
||||
struct fat_header *fat_header = (struct fat_header *)[head bytes];
|
||||
|
||||
// Get number of architectures in the binary
|
||||
uint32_t narch = NSSwapBigIntToHost(fat_header->nfat_arch);
|
||||
|
||||
// Retrieve just the fat_arch's, make a mutable copy and if necessary swap the bytes
|
||||
NSData *archs = [self safeSubdataWithRange:NSMakeRange(sizeof(struct fat_header),
|
||||
sizeof(struct fat_arch) * narch)];
|
||||
if (!archs) return nil;
|
||||
struct fat_arch *fat_archs = (struct fat_arch *)[archs bytes];
|
||||
|
||||
// For each arch, get the name of it's architecture
|
||||
for (uint32_t i = 0; i < narch; ++i) {
|
||||
cpu_type_t cpu = (cpu_type_t)NSSwapBigIntToHost((unsigned int)fat_archs[i].cputype);
|
||||
[ret addObject:[self nameForCPUType:cpu]];
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
struct mach_header *hdr = [self firstMachHeader];
|
||||
return @[ [self nameForCPUType:hdr->cputype] ];
|
||||
}
|
||||
return nil;
|
||||
return [self.machHeaders allKeys];
|
||||
}
|
||||
|
||||
- (BOOL)isDylib {
|
||||
- (uint32_t)machFileType {
|
||||
struct mach_header *mach_header = [self firstMachHeader];
|
||||
if (!mach_header) return NO;
|
||||
if (mach_header->filetype == MH_DYLIB || mach_header->filetype == MH_FVMLIB) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)isKext {
|
||||
struct mach_header *mach_header = [self firstMachHeader];
|
||||
if (!mach_header) return NO;
|
||||
if (mach_header->filetype == MH_KEXT_BUNDLE) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)isMachO {
|
||||
return ([self.fileData length] >= 160 &&
|
||||
([self isMachHeader:(struct mach_header *)[self.fileData bytes]] || [self isFat]));
|
||||
}
|
||||
|
||||
- (BOOL)isFat {
|
||||
return ([self isFatHeader:(struct fat_header *)[self.fileData bytes]]);
|
||||
}
|
||||
|
||||
- (BOOL)isScript {
|
||||
if ([self.fileData length] < 1) return NO;
|
||||
|
||||
char magic[2];
|
||||
[self.fileData getBytes:&magic length:2];
|
||||
|
||||
return (strncmp("#!", magic, 2) == 0);
|
||||
if (mach_header) return mach_header->filetype;
|
||||
return -1;
|
||||
}
|
||||
|
||||
- (BOOL)isExecutable {
|
||||
struct mach_header *mach_header = [self firstMachHeader];
|
||||
if (!mach_header) return NO;
|
||||
if (mach_header->filetype == MH_OBJECT ||
|
||||
mach_header->filetype == MH_EXECUTE ||
|
||||
mach_header->filetype == MH_PRELOAD) {
|
||||
return YES;
|
||||
}
|
||||
return [self machFileType] == MH_EXECUTE;
|
||||
}
|
||||
|
||||
return NO;
|
||||
- (BOOL)isDylib {
|
||||
return [self machFileType] == MH_DYLIB;
|
||||
}
|
||||
|
||||
- (BOOL)isBundle {
|
||||
return [self machFileType] == MH_BUNDLE;
|
||||
}
|
||||
|
||||
- (BOOL)isKext {
|
||||
return [self machFileType] == MH_KEXT_BUNDLE;
|
||||
}
|
||||
|
||||
- (BOOL)isMachO {
|
||||
return (self.machHeaders.count > 0);
|
||||
}
|
||||
|
||||
- (BOOL)isFat {
|
||||
return (self.machHeaders.count > 1);
|
||||
}
|
||||
|
||||
- (BOOL)isScript {
|
||||
const char *magic = (const char *)[[self safeSubdataWithRange:NSMakeRange(0, 2)] bytes];
|
||||
return (magic && memcmp("#!", magic, 2) == 0);
|
||||
}
|
||||
|
||||
- (BOOL)isXARArchive {
|
||||
const char *magic = (const char *)[[self safeSubdataWithRange:NSMakeRange(0, 4)] bytes];
|
||||
return (magic && memcmp("xar!", magic, 4) == 0);
|
||||
}
|
||||
|
||||
- (BOOL)isDMG {
|
||||
if (self.fileSize < 512) return NO;
|
||||
NSUInteger last512 = self.fileSize - 512;
|
||||
const char *magic = (const char *)[[self safeSubdataWithRange:NSMakeRange(last512, 4)] bytes];
|
||||
return (magic && memcmp("koly", magic, 4) == 0);
|
||||
}
|
||||
|
||||
- (NSString *)humanReadableFileType {
|
||||
if ([self isExecutable]) return @"Executable";
|
||||
if ([self isDylib]) return @"Dynamic Library";
|
||||
if ([self isBundle]) return @"Bundle/Plugin";
|
||||
if ([self isKext]) return @"Kernel Extension";
|
||||
if ([self isScript]) return @"Script";
|
||||
if ([self isXARArchive]) return @"XAR Archive";
|
||||
if ([self isDMG]) return @"Disk Image";
|
||||
return @"Unknown";
|
||||
}
|
||||
|
||||
#pragma mark Page Zero
|
||||
|
||||
- (BOOL)isMissingPageZero {
|
||||
// This method only checks i386 arch because the kernel enforces this for other archs
|
||||
// See bsd/kern/mach_loader.c, search for enforce_hard_pagezero.
|
||||
MachHeaderWithOffset *x86Header = self.machHeaders[[self nameForCPUType:CPU_TYPE_X86
|
||||
cpuSubType:CPU_SUBTYPE_I386_ALL]];
|
||||
if (!x86Header) return NO;
|
||||
|
||||
struct mach_header *mh = (struct mach_header *)[x86Header.data bytes];
|
||||
if (mh->filetype != MH_EXECUTE) return NO;
|
||||
|
||||
NSRange range = NSMakeRange(x86Header.offset + sizeof(struct mach_header),
|
||||
sizeof(struct segment_command));
|
||||
NSData *lcData = [self safeSubdataWithRange:range];
|
||||
if (!lcData) return NO;
|
||||
|
||||
// This code assumes the __PAGEZERO is always the first load-command in the file.
|
||||
// Given that the macOS ABI says "the static linker creates a __PAGEZERO segment
|
||||
// as the first segment of an executable file." this should be OK.
|
||||
struct load_command *lc = (struct load_command *)[lcData bytes];
|
||||
if (lc->cmd == LC_SEGMENT) {
|
||||
struct segment_command *segment = (struct segment_command *)lc;
|
||||
if (segment->vmaddr == 0 && segment->vmsize != 0 &&
|
||||
segment->initprot == 0 && segment->maxprot == 0 &&
|
||||
strcmp("__PAGEZERO", segment->segname) == 0) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark Bundle Information
|
||||
|
||||
///
|
||||
/// Directories with a "Contents/Info.plist" entry can be mistaken as a bundle. To be considered an
|
||||
/// ancestor, the bundle must have a valid extension.
|
||||
///
|
||||
- (NSSet *)allowedAncestorExtensions {
|
||||
static NSSet *set;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
set = [NSSet setWithArray:@[
|
||||
@"app",
|
||||
@"bundle",
|
||||
@"framework",
|
||||
@"kext",
|
||||
@"xctest",
|
||||
@"xpc",
|
||||
]];
|
||||
});
|
||||
return set;
|
||||
}
|
||||
|
||||
///
|
||||
/// Try and determine the bundle that the represented executable is contained within, if any.
|
||||
///
|
||||
/// Rationale: An NSBundle has a method executablePath for discovering the main binary within a
|
||||
/// bundle but provides no way to get an NSBundle object when only the executablePath is known.
|
||||
/// Also a bundle can contain multiple binaries within the MacOS folder and we want any of these
|
||||
/// Also a bundle can contain multiple binaries within its subdirectories and we want any of these
|
||||
/// to count as being part of the bundle.
|
||||
///
|
||||
/// This method relies on executable bundles being laid out as follows:
|
||||
/// This method walks up the path until a bundle is found, if any.
|
||||
///
|
||||
/// @code
|
||||
/// Bundle.app/
|
||||
/// Contents/
|
||||
/// MacOS/
|
||||
/// executable
|
||||
/// @endcode
|
||||
///
|
||||
/// If @c self.path is the full path to @c executable above, this method would return an
|
||||
/// NSBundle reference for Bundle.app.
|
||||
/// @param ancestor YES this will return the highest NSBundle, with a valid extension, found in the
|
||||
/// tree. NO will return the the lowest NSBundle, without validating the extension.
|
||||
///
|
||||
- (NSBundle *)findBundleWithAncestor:(BOOL)ancestor {
|
||||
NSBundle *bundle;
|
||||
NSMutableArray *pathComponents = [[self.path pathComponents] mutableCopy];
|
||||
|
||||
// Ignore the root path "/", for some reason this is considered a bundle.
|
||||
while (pathComponents.count > 1) {
|
||||
NSBundle *bndl = [NSBundle bundleWithPath:[NSString pathWithComponents:pathComponents]];
|
||||
if ([bndl objectForInfoDictionaryKey:@"CFBundleIdentifier"]) {
|
||||
if (!ancestor ||
|
||||
[[self allowedAncestorExtensions] containsObject:bndl.bundlePath.pathExtension]) {
|
||||
bundle = bndl;
|
||||
}
|
||||
if (!ancestor) break;
|
||||
}
|
||||
[pathComponents removeLastObject];
|
||||
}
|
||||
return bundle;
|
||||
}
|
||||
|
||||
- (NSBundle *)bundle {
|
||||
if (self.bundleRef) return self.bundleRef;
|
||||
|
||||
NSArray *pathComponents = [self.path pathComponents];
|
||||
|
||||
// Check that the full path is at least 4-levels deep:
|
||||
// e.g: /Calendar.app/Contents/MacOS/Calendar
|
||||
if ([pathComponents count] < 4) return nil;
|
||||
|
||||
pathComponents = [pathComponents subarrayWithRange:NSMakeRange(0, [pathComponents count] - 3)];
|
||||
self.bundleRef = [NSBundle bundleWithPath:[NSString pathWithComponents:pathComponents]];
|
||||
|
||||
// Clear the bundle if it doesn't have a bundle ID
|
||||
if (![self.bundleRef objectForInfoDictionaryKey:@"CFBundleIdentifier"]) self.bundleRef = nil;
|
||||
|
||||
return self.bundleRef;
|
||||
if (!self.bundleRef) {
|
||||
self.bundleRef =
|
||||
[self findBundleWithAncestor:self.useAncestorBundle] ?: (NSBundle *)[NSNull null];
|
||||
}
|
||||
return self.bundleRef == (NSBundle *)[NSNull null] ? nil : self.bundleRef;
|
||||
}
|
||||
|
||||
- (NSString *)bundlePath {
|
||||
return [self.bundle bundlePath];
|
||||
}
|
||||
|
||||
- (NSDictionary *)infoPlist {
|
||||
if (self.infoDict) return self.infoDict;
|
||||
|
||||
if ([self bundle]) {
|
||||
self.infoDict = [[self bundle] infoDictionary];
|
||||
return self.infoDict;
|
||||
- (void)setUseAncestorBundle:(BOOL)useAncestorBundle {
|
||||
if (self.useAncestorBundle != useAncestorBundle) {
|
||||
self.bundleRef = nil;
|
||||
self.infoDict = nil;
|
||||
}
|
||||
_useAncestorBundle = useAncestorBundle;
|
||||
}
|
||||
|
||||
NSURL *url = [NSURL fileURLWithPath:self.path isDirectory:NO];
|
||||
self.infoDict =
|
||||
(__bridge_transfer NSDictionary *)CFBundleCopyInfoDictionaryForURL((__bridge CFURLRef)url);
|
||||
return self.infoDict;
|
||||
- (NSDictionary *)infoPlist {
|
||||
if (!self.infoDict) {
|
||||
NSDictionary *d = [self embeddedPlist];
|
||||
if (d) {
|
||||
self.infoDict = d;
|
||||
return self.infoDict;
|
||||
}
|
||||
|
||||
d = self.bundle.infoDictionary;
|
||||
if (d) {
|
||||
self.infoDict = d;
|
||||
return self.infoDict;
|
||||
}
|
||||
|
||||
self.infoDict = (NSDictionary *)[NSNull null];
|
||||
}
|
||||
return self.infoDict == (NSDictionary *)[NSNull null] ? nil : self.infoDict;
|
||||
}
|
||||
|
||||
- (NSString *)bundleIdentifier {
|
||||
return [[self infoPlist] objectForKey:@"CFBundleIdentifier"];
|
||||
return [[self.infoPlist objectForKey:@"CFBundleIdentifier"] description];
|
||||
}
|
||||
|
||||
- (NSString *)bundleName {
|
||||
return [[self infoPlist] objectForKey:@"CFBundleName"];
|
||||
return [[self.infoPlist objectForKey:@"CFBundleDisplayName"] description] ?:
|
||||
[[self.infoPlist objectForKey:@"CFBundleName"] description];
|
||||
}
|
||||
|
||||
- (NSString *)bundleVersion {
|
||||
return [[self infoPlist] objectForKey:@"CFBundleVersion"];
|
||||
return [[self.infoPlist objectForKey:@"CFBundleVersion"] description];
|
||||
}
|
||||
|
||||
- (NSString *)bundleShortVersionString {
|
||||
return [[self infoPlist] objectForKey:@"CFBundleShortVersionString"];
|
||||
return [[self.infoPlist objectForKey:@"CFBundleShortVersionString"] description];
|
||||
}
|
||||
|
||||
- (NSArray *)downloadURLs {
|
||||
char *path = (char *)[self.path fileSystemRepresentation];
|
||||
size_t size = (size_t)getxattr(path, "com.apple.metadata:kMDItemWhereFroms", NULL, 0, 0, 0);
|
||||
char *value = malloc(size);
|
||||
if (!value) return nil;
|
||||
#pragma mark Quarantine Data
|
||||
|
||||
if (getxattr(path, "com.apple.metadata:kMDItemWhereFroms", value, size, 0, 0) == -1) {
|
||||
free(value);
|
||||
return nil;
|
||||
}
|
||||
- (NSString *)quarantineDataURL {
|
||||
NSURL *dataURL = [self quarantineData][@"LSQuarantineDataURL"];
|
||||
if (dataURL == (NSURL *)[NSNull null]) dataURL = nil;
|
||||
return [dataURL absoluteString];
|
||||
}
|
||||
|
||||
NSData *data = [NSData dataWithBytes:value length:size];
|
||||
free(value);
|
||||
- (NSString *)quarantineRefererURL {
|
||||
NSURL *originURL = [self quarantineData][@"LSQuarantineOriginURL"];
|
||||
if (originURL == (NSURL *)[NSNull null]) originURL = nil;
|
||||
return [originURL absoluteString];
|
||||
}
|
||||
|
||||
if (data) {
|
||||
NSArray *urls = [NSPropertyListSerialization propertyListWithData:data
|
||||
options:NSPropertyListImmutable
|
||||
format:NULL
|
||||
error:NULL];
|
||||
return urls;
|
||||
}
|
||||
- (NSString *)quarantineAgentBundleID {
|
||||
NSString *agentBundle = [self quarantineData][@"LSQuarantineAgentBundleIdentifier"];
|
||||
if (agentBundle == (NSString *)[NSNull null]) agentBundle = nil;
|
||||
return agentBundle;
|
||||
}
|
||||
|
||||
return nil;
|
||||
- (NSDate *)quarantineTimestamp {
|
||||
NSDate *timeStamp = [self quarantineData][@"LSQuarantineTimeStamp"];
|
||||
return timeStamp;
|
||||
}
|
||||
|
||||
#pragma mark Internal Methods
|
||||
|
||||
///
|
||||
/// Look through the file for the first mach_header. If the file is thin, this will be the
|
||||
/// header at the beginning of the file. If the file is fat, it will be the first
|
||||
/// architecture-specific header.
|
||||
///
|
||||
- (struct mach_header *)firstMachHeader {
|
||||
if (![self isMachO]) return NULL;
|
||||
- (NSDictionary *)machHeaders {
|
||||
if (self.cachedHeaders) return self.cachedHeaders;
|
||||
|
||||
struct mach_header *mach_header = (struct mach_header *)[self.fileData bytes];
|
||||
struct fat_header *fat_header = (struct fat_header *)[self.fileData bytes];
|
||||
|
||||
if ([self isFatHeader:fat_header]) {
|
||||
// Get the bytes for the fat_arch
|
||||
NSData *archHdr =
|
||||
[self safeSubdataWithRange:NSMakeRange(sizeof(struct fat_header), sizeof(struct fat_arch))];
|
||||
if (!archHdr) return nil;
|
||||
struct fat_arch *fat_arch = (struct fat_arch *)[archHdr bytes];
|
||||
|
||||
// Get bytes for first mach_header
|
||||
NSData *machHdr = [self safeSubdataWithRange:NSMakeRange(NSSwapBigIntToHost(fat_arch->offset),
|
||||
sizeof(struct mach_header))];
|
||||
if (!machHdr) return nil;
|
||||
mach_header = (struct mach_header *)[machHdr bytes];
|
||||
// Sanity check file length
|
||||
if (self.fileSize < sizeof(struct mach_header)) {
|
||||
self.cachedHeaders = [NSDictionary dictionary];
|
||||
return self.cachedHeaders;
|
||||
}
|
||||
|
||||
if ([self isMachHeader:mach_header]) {
|
||||
return mach_header;
|
||||
NSMutableDictionary *machHeaders = [NSMutableDictionary dictionary];
|
||||
|
||||
NSData *machHeader = [self parseSingleMachHeader:[self safeSubdataWithRange:NSMakeRange(0,
|
||||
4096)]];
|
||||
if (machHeader) {
|
||||
struct mach_header *mh = (struct mach_header *)[machHeader bytes];
|
||||
MachHeaderWithOffset *mhwo = [[MachHeaderWithOffset alloc] initWithData:machHeader offset:0];
|
||||
machHeaders[[self nameForCPUType:mh->cputype cpuSubType:mh->cpusubtype]] = mhwo;
|
||||
} else {
|
||||
NSRange range = NSMakeRange(0, sizeof(struct fat_header));
|
||||
NSData *fatHeader = [self safeSubdataWithRange:range];
|
||||
struct fat_header *fh = (struct fat_header *)[fatHeader bytes];
|
||||
|
||||
if (fatHeader && (fh->magic == FAT_CIGAM || fh->magic == FAT_MAGIC)) {
|
||||
int nfat_arch = OSSwapBigToHostInt32(fh->nfat_arch);
|
||||
range = NSMakeRange(sizeof(struct fat_header), sizeof(struct fat_arch) * nfat_arch);
|
||||
NSMutableData *fatArchs = [[self safeSubdataWithRange:range] mutableCopy];
|
||||
if (fatArchs) {
|
||||
struct fat_arch *fat_arch = (struct fat_arch *)[fatArchs mutableBytes];
|
||||
for (int i = 0; i < nfat_arch; ++i) {
|
||||
int offset = OSSwapBigToHostInt32(fat_arch[i].offset);
|
||||
int size = OSSwapBigToHostInt32(fat_arch[i].size);
|
||||
int cputype = OSSwapBigToHostInt(fat_arch[i].cputype);
|
||||
int cpusubtype = OSSwapBigToHostInt(fat_arch[i].cpusubtype);
|
||||
|
||||
range = NSMakeRange(offset, size);
|
||||
NSData *machHeader = [self parseSingleMachHeader:[self safeSubdataWithRange:range]];
|
||||
if (machHeader) {
|
||||
NSString *key = [self nameForCPUType:cputype cpuSubType:cpusubtype];
|
||||
MachHeaderWithOffset *mhwo = [[MachHeaderWithOffset alloc] initWithData:machHeader
|
||||
offset:offset];
|
||||
machHeaders[key] = mhwo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
self.cachedHeaders = [machHeaders copy];
|
||||
return self.cachedHeaders;
|
||||
}
|
||||
|
||||
- (BOOL)isMachHeader:(struct mach_header *)header {
|
||||
return (header->magic == MH_MAGIC || header->magic == MH_MAGIC_64 ||
|
||||
header->magic == MH_CIGAM || header->magic == MH_CIGAM_64);
|
||||
}
|
||||
- (NSData *)parseSingleMachHeader:(NSData *)inputData {
|
||||
if (inputData.length < sizeof(struct mach_header)) return nil;
|
||||
struct mach_header *mh = (struct mach_header *)[inputData bytes];
|
||||
|
||||
- (BOOL)isFatHeader:(struct fat_header *)header {
|
||||
return (header->magic == FAT_MAGIC || header->magic == FAT_CIGAM);
|
||||
if (mh->magic == MH_CIGAM || mh->magic == MH_CIGAM_64) {
|
||||
NSMutableData *mutableInput = [inputData mutableCopy];
|
||||
mh = (struct mach_header *)[mutableInput mutableBytes];
|
||||
swap_mach_header(mh, NXHostByteOrder());
|
||||
}
|
||||
|
||||
if (mh->magic == MH_MAGIC || mh->magic == MH_MAGIC_64) {
|
||||
return [NSData dataWithBytes:mh length:sizeof(struct mach_header)];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
///
|
||||
/// Wrap @c subdataWithRange: in a @@try/@@catch, returning nil on exception.
|
||||
/// Useful for when the range is beyond the end of the file.
|
||||
/// Locate an embedded plist in the file
|
||||
///
|
||||
- (NSData *)safeSubdataWithRange:(NSRange)range {
|
||||
@try {
|
||||
return [self.fileData subdataWithRange:range];
|
||||
}
|
||||
@catch (NSException *e) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
- (NSDictionary *)embeddedPlist {
|
||||
// Look for an embedded Info.plist if there is one.
|
||||
// This could (and used to) use CFBundleCopyInfoDictionaryForURL but that uses mmap to read
|
||||
// the file and so can cause SIGBUS if the file is deleted/truncated while it's working.
|
||||
MachHeaderWithOffset *mhwo = [[self.machHeaders allValues] firstObject];
|
||||
if (!mhwo) return nil;
|
||||
|
||||
- (NSString *)nameForCPUType:(cpu_type_t)cpuType {
|
||||
switch (cpuType) {
|
||||
case CPU_TYPE_X86:
|
||||
return @"i386";
|
||||
case CPU_TYPE_X86_64:
|
||||
return @"x86-64";
|
||||
case CPU_TYPE_POWERPC:
|
||||
return @"ppc";
|
||||
case CPU_TYPE_POWERPC64:
|
||||
return @"ppc64";
|
||||
default:
|
||||
return @"unknown";
|
||||
struct mach_header *mh = (struct mach_header *)mhwo.data.bytes;
|
||||
if (mh->filetype != MH_EXECUTE) return self.infoDict;
|
||||
BOOL is64 = (mh->magic == MH_MAGIC_64 || mh->magic == MH_CIGAM_64);
|
||||
uint32_t ncmds = mh->ncmds;
|
||||
uint32_t nsects = 0;
|
||||
uint64_t offset = mhwo.offset;
|
||||
|
||||
uint32_t sz_header = is64 ? sizeof(struct mach_header_64) : sizeof(struct mach_header);
|
||||
uint32_t sz_segment = is64 ? sizeof(struct segment_command_64) : sizeof(struct segment_command);
|
||||
uint32_t sz_section = is64 ? sizeof(struct section_64) : sizeof(struct section);
|
||||
|
||||
offset += sz_header;
|
||||
|
||||
// Loop through the load commands looking for the segment named __TEXT
|
||||
for (uint32_t i = 0; i < ncmds; ++i) {
|
||||
NSData *cmdData = [self safeSubdataWithRange:NSMakeRange(offset, sz_segment)];
|
||||
if (!cmdData) return nil;
|
||||
struct segment_command_64 *lc = (struct segment_command_64 *)[cmdData bytes];
|
||||
if (lc->cmd == LC_SEGMENT || lc->cmd == LC_SEGMENT_64) {
|
||||
if (memcmp(lc->segname, "__TEXT", 6) == 0) {
|
||||
nsects = lc->nsects;
|
||||
offset += sz_segment;
|
||||
break;
|
||||
}
|
||||
}
|
||||
offset += lc->cmdsize;
|
||||
}
|
||||
|
||||
// Loop through the sections in the __TEXT segment looking for an __info_plist section.
|
||||
for (uint32_t i = 0; i < nsects; ++i) {
|
||||
NSData *sectData = [self safeSubdataWithRange:NSMakeRange(offset, sz_section)];
|
||||
if (!sectData) return nil;
|
||||
struct section_64 *sect = (struct section_64 *)[sectData bytes];
|
||||
if (sect && memcmp(sect->sectname, "__info_plist", 12) == 0 && sect->size < 2000000) {
|
||||
NSData *plistData = [self safeSubdataWithRange:NSMakeRange(sect->offset, sect->size)];
|
||||
if (!plistData) return nil;
|
||||
NSDictionary *plist;
|
||||
plist = [NSPropertyListSerialization propertyListWithData:plistData
|
||||
options:NSPropertyListImmutable
|
||||
format:NULL
|
||||
error:NULL];
|
||||
if (plist) return plist;
|
||||
}
|
||||
offset += sz_section;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
///
|
||||
/// Return the first mach_header in this file.
|
||||
///
|
||||
- (struct mach_header *)firstMachHeader {
|
||||
return (struct mach_header *)([[[[self.machHeaders allValues] firstObject] data] bytes]);
|
||||
}
|
||||
|
||||
///
|
||||
/// Extract a range of the file as an NSData, handling any exceptions.
|
||||
/// Returns nil if the requested range is outside of the range of the file.
|
||||
///
|
||||
- (NSData *)safeSubdataWithRange:(NSRange)range {
|
||||
@try {
|
||||
if ((range.location + range.length) > self.fileSize) return nil;
|
||||
[self.fileHandle seekToFileOffset:range.location];
|
||||
NSData *d = [self.fileHandle readDataOfLength:range.length];
|
||||
if (d.length != range.length) return nil;
|
||||
return d;
|
||||
} @catch (NSException *e) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Retrieve quarantine data for a file and caches the dictionary
|
||||
/// This method attempts to handle fetching the quarantine data even if the running user
|
||||
/// is not the one who downloaded the file.
|
||||
///
|
||||
- (NSDictionary *)quarantineData {
|
||||
if (!self.quarantineDict && self.fileOwnerHomeDir && NSURLQuarantinePropertiesKey) {
|
||||
self.quarantineDict = (NSDictionary *)[NSNull null];
|
||||
|
||||
NSURL *url = [NSURL fileURLWithPath:self.path];
|
||||
NSDictionary *d = [url resourceValuesForKeys:@[ NSURLQuarantinePropertiesKey ] error:NULL];
|
||||
|
||||
if (d[NSURLQuarantinePropertiesKey]) {
|
||||
d = d[NSURLQuarantinePropertiesKey];
|
||||
|
||||
if (d[@"LSQuarantineIsOwnedByCurrentUser"]) {
|
||||
self.quarantineDict = d;
|
||||
} else if (d[@"LSQuarantineEventIdentifier"]) {
|
||||
NSMutableDictionary *quarantineDict = [d mutableCopy];
|
||||
|
||||
// If self.path is on a quarantine disk image, LSQuarantineDiskImageURL will point to the
|
||||
// disk image and self.fileOwnerHomeDir will be incorrect (probably root).
|
||||
NSString *fileOwnerHomeDir = self.fileOwnerHomeDir;
|
||||
if (d[@"LSQuarantineDiskImageURL"]) {
|
||||
struct stat fileStat;
|
||||
stat([d[@"LSQuarantineDiskImageURL"] fileSystemRepresentation], &fileStat);
|
||||
if (fileStat.st_uid != 0) {
|
||||
struct passwd *pwd = getpwuid(fileStat.st_uid);
|
||||
if (pwd) {
|
||||
fileOwnerHomeDir = @(pwd->pw_dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NSURL *dbPath = [NSURL fileURLWithPathComponents:@[
|
||||
fileOwnerHomeDir,
|
||||
@"Library",
|
||||
@"Preferences",
|
||||
@"com.apple.LaunchServices.QuarantineEventsV2"
|
||||
]];
|
||||
FMDatabase *db = [FMDatabase databaseWithPath:[dbPath absoluteString]];
|
||||
db.logsErrors = NO;
|
||||
if ([db open]) {
|
||||
FMResultSet *rs = [db executeQuery:@"SELECT * FROM LSQuarantineEvent "
|
||||
@"WHERE LSQuarantineEventIdentifier=?",
|
||||
d[@"LSQuarantineEventIdentifier"]];
|
||||
if ([rs next]) {
|
||||
NSString *agentBundleID = [rs stringForColumn:@"LSQuarantineAgentBundleIdentifier"];
|
||||
NSString *dataURLString = [rs stringForColumn:@"LSQuarantineDataURLString"];
|
||||
NSString *originURLString = [rs stringForColumn:@"LSQuarantineOriginURLString"];
|
||||
double timeStamp = [rs doubleForColumn:@"LSQuarantineTimeStamp"];
|
||||
|
||||
quarantineDict[@"LSQuarantineAgentBundleIdentifier"] = agentBundleID;
|
||||
quarantineDict[@"LSQuarantineDataURL"] = [NSURL URLWithString:dataURLString];
|
||||
quarantineDict[@"LSQuarantineOriginURL"] = [NSURL URLWithString:originURLString];
|
||||
quarantineDict[@"LSQuarantineTimestamp"] =
|
||||
[NSDate dateWithTimeIntervalSinceReferenceDate:timeStamp];
|
||||
|
||||
self.quarantineDict = quarantineDict;
|
||||
}
|
||||
[rs close];
|
||||
[db close];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (self.quarantineDict == (NSDictionary *)[NSNull null]) ? nil : self.quarantineDict;
|
||||
}
|
||||
|
||||
///
|
||||
/// Return a human-readable string for a cpu_type_t.
|
||||
///
|
||||
- (NSString *)nameForCPUType:(cpu_type_t)cpuType cpuSubType:(cpu_subtype_t)cpuSubType {
|
||||
const NXArchInfo *archInfo = NXGetArchInfoFromCpuType(cpuType, cpuSubType);
|
||||
NSString *arch;
|
||||
if (archInfo && archInfo->name) {
|
||||
arch = @(archInfo->name);
|
||||
} else {
|
||||
arch = [NSString stringWithFormat:@"%i:%i", cpuType, cpuSubType];
|
||||
}
|
||||
return arch;
|
||||
}
|
||||
|
||||
///
|
||||
/// Resolves a given path:
|
||||
/// + Follows symlinks
|
||||
/// + Converts relative paths to absolute
|
||||
/// + If path is a directory, checks to see if that directory is a bundle and if so
|
||||
/// returns the path to that bundles CFBundleExecutable and stores a reference to the
|
||||
/// bundle in the bundle out-param.
|
||||
///
|
||||
- (NSString *)resolvePath:(NSString *)path bundle:(NSBundle **)bundle {
|
||||
// Convert to absolute, standardized path
|
||||
path = [path stringByResolvingSymlinksInPath];
|
||||
if (![path isAbsolutePath]) {
|
||||
NSString *cwd = [[NSFileManager defaultManager] currentDirectoryPath];
|
||||
path = [cwd stringByAppendingPathComponent:path];
|
||||
}
|
||||
path = [path stringByStandardizingPath];
|
||||
|
||||
// Determine if file exists.
|
||||
// If path is actually a directory, check to see if it's a bundle and has a CFBundleExecutable.
|
||||
BOOL directory;
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&directory]) {
|
||||
return nil;
|
||||
} else if (directory && ![path isEqualToString:@"/"]) {
|
||||
NSBundle *bndl = [NSBundle bundleWithPath:path];
|
||||
if (bundle) *bundle = bndl;
|
||||
return [bndl executablePath];
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Cache and return a MOLCodeSignChecker for the given file. If there was an error creating the
|
||||
/// code sign checker it will be returned in the passed-in error parameter.
|
||||
///
|
||||
- (MOLCodesignChecker *)codesignCheckerWithError:(NSError **)error {
|
||||
if (!self.cachedCodesignChecker && !self.codesignCheckerError) {
|
||||
NSError *e;
|
||||
self.cachedCodesignChecker = [[MOLCodesignChecker alloc] initWithBinaryPath:self.path error:&e];
|
||||
self.codesignCheckerError = e;
|
||||
}
|
||||
if (error) *error = self.codesignCheckerError;
|
||||
return self.cachedCodesignChecker;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
241
Source/common/SNTFileInfoTest.m
Normal file
241
Source/common/SNTFileInfoTest.m
Normal file
@@ -0,0 +1,241 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "Source/common/SNTFileInfo.h"
|
||||
|
||||
@interface SNTFileInfoTest : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation SNTFileInfoTest
|
||||
|
||||
- (NSString *)directoryBundle {
|
||||
NSString *rp = [[NSBundle bundleForClass:[self class]] resourcePath];
|
||||
return [rp stringByAppendingPathComponent:@"testdata/DirectoryBundle"];
|
||||
}
|
||||
|
||||
- (NSString *)bundleExample {
|
||||
NSString *rp = [[NSBundle bundleForClass:[self class]] resourcePath];
|
||||
return [rp stringByAppendingPathComponent:@"testdata/BundleExample.app"];
|
||||
}
|
||||
|
||||
- (void)testPathStandardizing {
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:@"/Applications/Safari.app"];
|
||||
XCTAssertNotNil(sut);
|
||||
XCTAssertEqualObjects(sut.path, @"/Applications/Safari.app/Contents/MacOS/Safari");
|
||||
|
||||
sut = [[SNTFileInfo alloc] initWithPath:@"../../../../../../../../../../../../../../../bin/ls"];
|
||||
XCTAssertEqualObjects(sut.path, @"/bin/ls");
|
||||
|
||||
sut = [[SNTFileInfo alloc] initWithPath:@"/usr/sbin/AppleFileServer"];
|
||||
XCTAssertEqualObjects(sut.path, @"/System/Library/CoreServices/AppleFileServer.app/"
|
||||
@"Contents/MacOS/AppleFileServer");
|
||||
}
|
||||
|
||||
- (void)testSHA1 {
|
||||
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"missing_pagezero"
|
||||
ofType:@""];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
|
||||
XCTAssertNotNil(sut.SHA1);
|
||||
XCTAssertEqual(sut.SHA1.length, 40);
|
||||
XCTAssertEqualObjects(sut.SHA1, @"3a865bf47b4ceba20496e0e66e39e4cfa101ffe6");
|
||||
}
|
||||
|
||||
- (void)testSHA256 {
|
||||
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"missing_pagezero"
|
||||
ofType:@""];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
|
||||
XCTAssertNotNil(sut.SHA256);
|
||||
XCTAssertEqual(sut.SHA256.length, 64);
|
||||
XCTAssertEqualObjects(sut.SHA256,
|
||||
@"5e089b65a1e7a4696d84a34510710b6993d1de21250c41daaec63d9981083eba");
|
||||
}
|
||||
|
||||
- (void)testExecutable {
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:@"/sbin/launchd"];
|
||||
|
||||
XCTAssertTrue(sut.isMachO);
|
||||
XCTAssertTrue(sut.isExecutable);
|
||||
|
||||
XCTAssertFalse(sut.isDylib);
|
||||
XCTAssertFalse(sut.isFat);
|
||||
XCTAssertFalse(sut.isKext);
|
||||
XCTAssertFalse(sut.isScript);
|
||||
}
|
||||
|
||||
- (void)testPageZero {
|
||||
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"missing_pagezero"
|
||||
ofType:@""];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
XCTAssertTrue(sut.isMissingPageZero);
|
||||
|
||||
path = [[NSBundle bundleForClass:[self class]] pathForResource:@"bad_pagezero" ofType:@""];
|
||||
sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
XCTAssertTrue(sut.isMissingPageZero);
|
||||
|
||||
sut = [[SNTFileInfo alloc] initWithPath:@"/usr/sbin/bless"];
|
||||
XCTAssertFalse(sut.isMissingPageZero);
|
||||
}
|
||||
|
||||
- (void)testKext {
|
||||
SNTFileInfo *sut =
|
||||
[[SNTFileInfo alloc] initWithPath:
|
||||
@"/System/Library/Extensions/AppleAPIC.kext/Contents/MacOS/AppleAPIC"];
|
||||
|
||||
XCTAssertTrue(sut.isMachO);
|
||||
XCTAssertTrue(sut.isKext);
|
||||
|
||||
XCTAssertFalse(sut.isDylib);
|
||||
XCTAssertFalse(sut.isExecutable);
|
||||
XCTAssertFalse(sut.isFat);
|
||||
XCTAssertFalse(sut.isScript);
|
||||
}
|
||||
|
||||
- (void)testDylibs {
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:@"/usr/lib/libsqlite3.dylib"];
|
||||
|
||||
XCTAssertTrue(sut.isMachO);
|
||||
XCTAssertTrue(sut.isDylib);
|
||||
XCTAssertTrue(sut.isFat);
|
||||
|
||||
XCTAssertFalse(sut.isKext);
|
||||
XCTAssertFalse(sut.isExecutable);
|
||||
XCTAssertFalse(sut.isScript);
|
||||
}
|
||||
|
||||
- (void)testScript {
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:@"/usr/bin/h2ph"];
|
||||
|
||||
XCTAssertTrue(sut.isScript);
|
||||
|
||||
XCTAssertFalse(sut.isDylib);
|
||||
XCTAssertFalse(sut.isExecutable);
|
||||
XCTAssertFalse(sut.isFat);
|
||||
XCTAssertFalse(sut.isKext);
|
||||
XCTAssertFalse(sut.isMachO);
|
||||
}
|
||||
|
||||
- (void)testBundle {
|
||||
NSString *path = [self bundleExample];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
|
||||
XCTAssertNotNil([sut bundle]);
|
||||
|
||||
XCTAssertEqualObjects([sut bundleIdentifier], @"com.google.santa.BundleExample");
|
||||
XCTAssertEqualObjects([sut bundleName], @"BundleExample");
|
||||
XCTAssertEqualObjects([sut bundleVersion], @"1");
|
||||
XCTAssertEqualObjects([sut bundleShortVersionString], @"1.0");
|
||||
XCTAssertEqualObjects([sut bundlePath], path);
|
||||
}
|
||||
|
||||
- (void)testAncestorBundle {
|
||||
NSString *path = [self bundleExample];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
sut.useAncestorBundle = YES;
|
||||
|
||||
XCTAssertNotNil([sut bundle]);
|
||||
|
||||
XCTAssertEqualObjects([sut bundleIdentifier], @"com.google.santa.UnitTest.SNTFileInfoTest");
|
||||
XCTAssertNotNil([sut bundleVersion]);
|
||||
XCTAssertNotNil([sut bundleShortVersionString]);
|
||||
|
||||
NSString *ancestorBundlePath = path;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
ancestorBundlePath = [ancestorBundlePath stringByDeletingLastPathComponent];
|
||||
}
|
||||
XCTAssertEqualObjects([sut bundlePath], ancestorBundlePath);
|
||||
}
|
||||
|
||||
- (void)testBundleIsAncestor {
|
||||
NSString *path = [NSBundle bundleForClass:[self class]].bundlePath;
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
sut.useAncestorBundle = YES;
|
||||
|
||||
XCTAssertNotNil([sut bundle]);
|
||||
|
||||
XCTAssertEqualObjects([sut bundleIdentifier], @"com.google.santa.UnitTest.SNTFileInfoTest");
|
||||
XCTAssertNotNil([sut bundleVersion]);
|
||||
XCTAssertNotNil([sut bundleShortVersionString]);
|
||||
XCTAssertEqualObjects([sut bundlePath], path);
|
||||
}
|
||||
|
||||
- (void)testDirectoryBundleIsNotAncestor {
|
||||
NSString *path = [self directoryBundle];
|
||||
NSString *directoryBundle = @"/tmp/DirectoryBundle";
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
[fm removeItemAtPath:directoryBundle error:NULL];
|
||||
[fm copyItemAtPath:path toPath:directoryBundle error:NULL];
|
||||
path = [directoryBundle stringByAppendingString:@"/Contents/Resources/BundleExample.app"];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
sut.useAncestorBundle = YES;
|
||||
|
||||
XCTAssertNotNil([sut bundle]);
|
||||
|
||||
XCTAssertEqualObjects([sut bundleIdentifier], @"com.google.santa.BundleExample");
|
||||
XCTAssertEqualObjects([sut bundleName], @"BundleExample");
|
||||
XCTAssertEqualObjects([sut bundleVersion], @"1");
|
||||
XCTAssertEqualObjects([sut bundleShortVersionString], @"1.0");
|
||||
XCTAssertEqualObjects([sut bundlePath], path);
|
||||
}
|
||||
|
||||
- (void)testBundleCacheReset {
|
||||
NSString *path = [self bundleExample];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
|
||||
XCTAssertNotNil([sut bundle]);
|
||||
|
||||
XCTAssertEqualObjects([sut bundleIdentifier], @"com.google.santa.BundleExample");
|
||||
XCTAssertEqualObjects([sut bundleName], @"BundleExample");
|
||||
XCTAssertEqualObjects([sut bundleVersion], @"1");
|
||||
XCTAssertEqualObjects([sut bundleShortVersionString], @"1.0");
|
||||
XCTAssertEqualObjects([sut bundlePath], path);
|
||||
|
||||
sut.useAncestorBundle = YES;
|
||||
|
||||
XCTAssertNotNil([sut bundle]);
|
||||
|
||||
XCTAssertEqualObjects([sut bundleIdentifier], @"com.google.santa.UnitTest.SNTFileInfoTest");
|
||||
XCTAssertNotNil([sut bundleVersion]);
|
||||
XCTAssertNotNil([sut bundleShortVersionString]);
|
||||
|
||||
NSString *ancestorBundlePath = path;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
ancestorBundlePath = [ancestorBundlePath stringByDeletingLastPathComponent];
|
||||
}
|
||||
XCTAssertEqualObjects([sut bundlePath], ancestorBundlePath);
|
||||
}
|
||||
|
||||
- (void)testNonBundle {
|
||||
SNTFileInfo *sut =
|
||||
[[SNTFileInfo alloc] initWithPath:@"/usr/bin/yes"];
|
||||
|
||||
XCTAssertNil([sut bundle]);
|
||||
|
||||
sut.useAncestorBundle = YES;
|
||||
|
||||
XCTAssertNil([sut bundle]);
|
||||
}
|
||||
|
||||
- (void)testEmbeddedInfoPlist {
|
||||
// csreq is installed on all machines with Xcode installed. If you're running these tests,
|
||||
// it should be available..
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:@"/usr/bin/csreq"];
|
||||
|
||||
XCTAssertNotNil([sut infoPlist]);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,34 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
///
|
||||
/// Simple file watching class using dispatch sources. Will automatically
|
||||
/// reload the watch if the file is deleted. Will continue watching for
|
||||
/// events until deallocated.
|
||||
///
|
||||
@interface SNTFileWatcher : NSObject
|
||||
|
||||
///
|
||||
/// Designated initializer
|
||||
/// Initializes the watcher and begins watching for modifications.
|
||||
///
|
||||
/// @param filePath the file to watch.
|
||||
/// @param handler the handler to call when changes happen.
|
||||
///
|
||||
/// @note Shortly after the file has been opened and monitoring has begun, the provided handler
|
||||
/// will be called.
|
||||
///
|
||||
- (instancetype)initWithFilePath:(NSString *)filePath handler:(void (^)(void))handler;
|
||||
|
||||
@end
|
||||
@@ -1,100 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTFileWatcher.h"
|
||||
|
||||
@interface SNTFileWatcher ()
|
||||
@property NSString *filePath;
|
||||
@property dispatch_source_t monitoringSource;
|
||||
|
||||
@property(strong) void (^eventHandler)(void);
|
||||
@property(strong) void (^internalEventHandler)(void);
|
||||
@property(strong) void (^internalCancelHandler)(void);
|
||||
@end
|
||||
|
||||
@implementation SNTFileWatcher
|
||||
|
||||
- (instancetype)init {
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFilePath:(NSString *)filePath handler:(void (^)(void))handler {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_filePath = filePath;
|
||||
_eventHandler = handler;
|
||||
|
||||
if (!_filePath || !_eventHandler) return nil;
|
||||
|
||||
[self beginWatchingFile];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[self stopWatchingFile];
|
||||
}
|
||||
|
||||
- (void)beginWatchingFile {
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
int mask = (DISPATCH_VNODE_DELETE | DISPATCH_VNODE_WRITE |
|
||||
DISPATCH_VNODE_EXTEND | DISPATCH_VNODE_RENAME);
|
||||
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
|
||||
|
||||
self.internalEventHandler = ^{
|
||||
unsigned long l = dispatch_source_get_data(weakSelf.monitoringSource);
|
||||
if (l & DISPATCH_VNODE_DELETE || l & DISPATCH_VNODE_RENAME) {
|
||||
if (weakSelf.monitoringSource) dispatch_source_cancel(weakSelf.monitoringSource);
|
||||
} else {
|
||||
weakSelf.eventHandler();
|
||||
}
|
||||
};
|
||||
|
||||
self.internalCancelHandler = ^{
|
||||
int fd;
|
||||
|
||||
if (weakSelf.monitoringSource) {
|
||||
fd = (int)dispatch_source_get_handle(weakSelf.monitoringSource);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
while ((fd = open([weakSelf.filePath fileSystemRepresentation], O_EVTONLY)) < 0) {
|
||||
usleep(1000);
|
||||
}
|
||||
|
||||
weakSelf.monitoringSource =
|
||||
dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd, mask, queue);
|
||||
dispatch_source_set_event_handler(weakSelf.monitoringSource, weakSelf.internalEventHandler);
|
||||
dispatch_source_set_cancel_handler(weakSelf.monitoringSource, weakSelf.internalCancelHandler);
|
||||
dispatch_resume(weakSelf.monitoringSource);
|
||||
|
||||
weakSelf.eventHandler();
|
||||
};
|
||||
|
||||
dispatch_async(queue, self.internalCancelHandler);
|
||||
}
|
||||
|
||||
- (void)stopWatchingFile {
|
||||
if (!self.monitoringSource) return;
|
||||
|
||||
int fd = (int)dispatch_source_get_handle(self.monitoringSource);
|
||||
dispatch_source_set_event_handler_f(self.monitoringSource, NULL);
|
||||
dispatch_source_set_cancel_handler(self.monitoringSource, ^{ close(fd); });
|
||||
|
||||
dispatch_source_cancel(self.monitoringSource);
|
||||
self.monitoringSource = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -21,59 +21,121 @@
|
||||
#ifndef SANTA__COMMON__KERNELCOMMON_H
|
||||
#define SANTA__COMMON__KERNELCOMMON_H
|
||||
|
||||
// Defines the lengths of paths and Vnode IDs passed around.
|
||||
#define MAX_VNODE_ID_STR 21 // digits in UINT64_MAX + 1 for NULL-terminator
|
||||
|
||||
// Defines the name of the userclient class and the driver bundle ID.
|
||||
#define USERCLIENT_CLASS "com_google_SantaDriver"
|
||||
#define USERCLIENT_ID "com.google.santa-driver"
|
||||
|
||||
// Branch prediction
|
||||
#define likely(x) __builtin_expect(!!(x), 1)
|
||||
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||
|
||||
// List of methods supported by the driver.
|
||||
enum SantaDriverMethods {
|
||||
kSantaUserClientOpen,
|
||||
kSantaUserClientAllowBinary,
|
||||
kSantaUserClientAllowCompiler,
|
||||
kSantaUserClientDenyBinary,
|
||||
kSantaUserClientAcknowledgeBinary,
|
||||
kSantaUserClientClearCache,
|
||||
kSantaUserClientRemoveCacheEntry,
|
||||
kSantaUserClientCacheCount,
|
||||
kSantaUserClientCheckCache,
|
||||
kSantaUserClientCacheBucketCount,
|
||||
kSantaUserClientFilemodPrefixFilterAdd,
|
||||
kSantaUserClientFilemodPrefixFilterReset,
|
||||
|
||||
// Any methods supported by the driver should be added above this line to
|
||||
// ensure this remains the count of methods.
|
||||
kSantaUserClientNMethods,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
QUEUETYPE_DECISION,
|
||||
QUEUETYPE_LOG,
|
||||
} santa_queuetype_t;
|
||||
|
||||
// Enum defining actions that can be passed down the IODataQueue and in
|
||||
// response methods.
|
||||
typedef enum {
|
||||
ACTION_UNSET = 0,
|
||||
|
||||
// CHECKBW
|
||||
ACTION_REQUEST_CHECKBW = 10,
|
||||
ACTION_RESPOND_CHECKBW_ALLOW = 11,
|
||||
ACTION_RESPOND_CHECKBW_DENY = 12,
|
||||
// REQUESTS
|
||||
ACTION_REQUEST_SHUTDOWN = 10,
|
||||
ACTION_REQUEST_BINARY = 11,
|
||||
|
||||
// RESPONSES
|
||||
ACTION_RESPOND_ALLOW = 20,
|
||||
ACTION_RESPOND_DENY = 21,
|
||||
ACTION_RESPOND_TOOLONG = 22,
|
||||
ACTION_RESPOND_ACK = 23,
|
||||
ACTION_RESPOND_ALLOW_COMPILER = 24,
|
||||
// The following response is stored only in the kernel decision cache.
|
||||
// It is removed by SNTCompilerController
|
||||
ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE = 25,
|
||||
|
||||
// NOTIFY
|
||||
ACTION_NOTIFY_EXEC_ALLOW_NODAEMON = 30,
|
||||
ACTION_NOTIFY_EXEC_ALLOW_CACHED = 31,
|
||||
ACTION_NOTIFY_EXEC_DENY_CACHED = 32,
|
||||
|
||||
// SHUTDOWN
|
||||
ACTION_REQUEST_SHUTDOWN = 90,
|
||||
ACTION_NOTIFY_EXEC = 30,
|
||||
ACTION_NOTIFY_WRITE = 31,
|
||||
ACTION_NOTIFY_RENAME = 32,
|
||||
ACTION_NOTIFY_LINK = 33,
|
||||
ACTION_NOTIFY_EXCHANGE = 34,
|
||||
ACTION_NOTIFY_DELETE = 35,
|
||||
ACTION_NOTIFY_WHITELIST = 36,
|
||||
|
||||
// ERROR
|
||||
ACTION_ERROR = 99,
|
||||
} santa_action_t;
|
||||
|
||||
#define CHECKBW_RESPONSE_VALID(x) \
|
||||
(x == ACTION_RESPOND_CHECKBW_ALLOW || x == ACTION_RESPOND_CHECKBW_DENY)
|
||||
#define RESPONSE_VALID(x) \
|
||||
(x == ACTION_RESPOND_ALLOW || \
|
||||
x == ACTION_RESPOND_DENY || \
|
||||
x == ACTION_RESPOND_ALLOW_COMPILER || \
|
||||
x == ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE)
|
||||
|
||||
// Struct to manage vnode IDs
|
||||
typedef struct santa_vnode_id_t {
|
||||
uint64_t fsid;
|
||||
uint64_t fileid;
|
||||
|
||||
#ifdef __cplusplus
|
||||
bool operator==(const santa_vnode_id_t& rhs) const {
|
||||
return fsid == rhs.fsid && fileid == rhs.fileid;
|
||||
}
|
||||
// This _must not_ be used for anything security-sensitive. It exists solely to make
|
||||
// the msleep/wakeup calls easier.
|
||||
uint64_t unsafe_simple_id() const {
|
||||
return (((uint64_t)fsid << 32) | fileid);
|
||||
}
|
||||
#endif
|
||||
} santa_vnode_id_t;
|
||||
|
||||
// Message struct that is sent down the IODataQueue.
|
||||
typedef struct {
|
||||
santa_action_t action;
|
||||
uint64_t vnode_id;
|
||||
uid_t userId;
|
||||
santa_vnode_id_t vnode_id;
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
pid_t pid;
|
||||
pid_t ppid;
|
||||
char path[MAXPATHLEN];
|
||||
char newpath[MAXPATHLEN];
|
||||
// For file events, this is the process name.
|
||||
// For exec requests, this is the parent process name.
|
||||
// While process names can technically be 4*MAXPATHLEN, that never
|
||||
// actually happens, so only take MAXPATHLEN and throw away any excess.
|
||||
char pname[MAXPATHLEN];
|
||||
|
||||
// For messages that originate from EndpointSecurity, this points to a copy of the message.
|
||||
void *es_message;
|
||||
|
||||
// For messages that originate from EndpointSecurity, this points to an NSArray of the arguments.
|
||||
void *args_array;
|
||||
} santa_message_t;
|
||||
|
||||
// Used for the kSantaUserClientCacheBucketCount request.
|
||||
typedef struct {
|
||||
uint16_t per_bucket[1024];
|
||||
uint64_t start;
|
||||
} santa_bucket_count_t;
|
||||
|
||||
#endif // SANTA__COMMON__KERNELCOMMON_H
|
||||
|
||||
@@ -21,37 +21,52 @@
|
||||
|
||||
#ifdef KERNEL
|
||||
|
||||
#include <IOKit/IOLib.h>
|
||||
|
||||
#ifdef DEBUG
|
||||
#define LOGD(...) IOLog("D santa-driver: " __VA_ARGS__); IOLog("\n");
|
||||
#define LOGD(format, ...) IOLog("D santa-driver: " format "\n", ##__VA_ARGS__);
|
||||
#else // DEBUG
|
||||
#define LOGD(...)
|
||||
#define LOGD(format, ...)
|
||||
#endif // DEBUG
|
||||
#define LOGI(...) IOLog("I santa-driver: " __VA_ARGS__); IOLog("\n")
|
||||
#define LOGW(...) IOLog("W santa-driver: " __VA_ARGS__); IOLog("\n")
|
||||
#define LOGE(...) IOLog("E santa-driver: " __VA_ARGS__); IOLog("\n")
|
||||
#define LOGI(format, ...) IOLog("I santa-driver: " format "\n", ##__VA_ARGS__);
|
||||
#define LOGW(format, ...) IOLog("W santa-driver: " format "\n", ##__VA_ARGS__);
|
||||
#define LOGE(format, ...) IOLog("E santa-driver: " format "\n", ##__VA_ARGS__);
|
||||
|
||||
#else // KERNEL
|
||||
|
||||
#define LOG_LEVEL_ERROR 1
|
||||
#define LOG_LEVEL_WARN 2
|
||||
#define LOG_LEVEL_INFO 3
|
||||
#define LOG_LEVEL_DEBUG 4
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef enum : NSUInteger {
|
||||
LOG_LEVEL_ERROR,
|
||||
LOG_LEVEL_WARN,
|
||||
LOG_LEVEL_INFO,
|
||||
LOG_LEVEL_DEBUG
|
||||
} LogLevel;
|
||||
|
||||
///
|
||||
/// Logging function.
|
||||
///
|
||||
/// @param level one of the levels defined above
|
||||
/// @param destination a FILE, generally should be stdout or stderr
|
||||
/// @param destination a FILE, generally stdout/stderr. If the file is closed, the log
|
||||
/// will instead be sent to syslog.
|
||||
/// @param format the printf style format string
|
||||
/// @param ... the arguments to format.
|
||||
///
|
||||
void logMessage(int level, FILE *destination, NSString *format, ...);
|
||||
void logMessage(LogLevel level, FILE *destination, NSString *format, ...)
|
||||
__attribute__((format(__NSString__, 3, 4)));
|
||||
|
||||
/// Simple logging macros
|
||||
#define LOGD(logFormat, ...) logMessage(LOG_LEVEL_DEBUG, stdout, logFormat, ##__VA_ARGS__);
|
||||
#define LOGI(logFormat, ...) logMessage(LOG_LEVEL_INFO, stdout, logFormat, ##__VA_ARGS__);
|
||||
#define LOGW(logFormat, ...) logMessage(LOG_LEVEL_WARN, stderr, logFormat, ##__VA_ARGS__);
|
||||
#define LOGE(logFormat, ...) logMessage(LOG_LEVEL_ERROR, stderr, logFormat, ##__VA_ARGS__);
|
||||
#define LOGD(logFormat, ...) logMessage(LOG_LEVEL_DEBUG, stdout, logFormat, ##__VA_ARGS__)
|
||||
#define LOGI(logFormat, ...) logMessage(LOG_LEVEL_INFO, stdout, logFormat, ##__VA_ARGS__)
|
||||
#define LOGW(logFormat, ...) logMessage(LOG_LEVEL_WARN, stderr, logFormat, ##__VA_ARGS__)
|
||||
#define LOGE(logFormat, ...) logMessage(LOG_LEVEL_ERROR, stderr, logFormat, ##__VA_ARGS__)
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern C
|
||||
#endif
|
||||
|
||||
#endif // KERNEL
|
||||
|
||||
|
||||
@@ -12,52 +12,92 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTLogging.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
|
||||
#import <asl.h>
|
||||
#import <pthread.h>
|
||||
|
||||
#ifdef DEBUG
|
||||
static int logLevel = LOG_LEVEL_DEBUG;
|
||||
static LogLevel logLevel = LOG_LEVEL_DEBUG;
|
||||
#else
|
||||
static int logLevel = LOG_LEVEL_INFO; // default to info
|
||||
static LogLevel logLevel = LOG_LEVEL_INFO; // default to info
|
||||
#endif
|
||||
|
||||
void logMessage(int level, FILE *destination, NSString *format, ...) {
|
||||
static NSDateFormatter *dateFormatter;
|
||||
void syslogClientDestructor(void *arg) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
asl_close((aslclient)arg);
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
|
||||
static BOOL useSyslog = NO;
|
||||
static NSString *binaryName;
|
||||
static dispatch_once_t pred;
|
||||
static pthread_key_t syslogKey = 0;
|
||||
|
||||
dispatch_once(&pred, ^{
|
||||
dateFormatter = [[NSDateFormatter alloc] init];
|
||||
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]];
|
||||
[dateFormatter setDateFormat:@"YYYY-MM-dd HH:mm:ss.SSS'Z"];
|
||||
binaryName = [[NSProcessInfo processInfo] processName];
|
||||
|
||||
binaryName = [[NSProcessInfo processInfo] processName];
|
||||
// If debug logging is enabled, the process must be restarted.
|
||||
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--debug"]) {
|
||||
logLevel = LOG_LEVEL_DEBUG;
|
||||
}
|
||||
|
||||
// If debug logging is enabled, the process must be restarted.
|
||||
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--debug"]) {
|
||||
logLevel = LOG_LEVEL_DEBUG;
|
||||
}
|
||||
// If requested, redirect output to syslog.
|
||||
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--syslog"] ||
|
||||
[binaryName isEqualToString:@"com.google.santa.daemon"]) {
|
||||
useSyslog = YES;
|
||||
pthread_key_create(&syslogKey, syslogClientDestructor);
|
||||
}
|
||||
});
|
||||
|
||||
if (logLevel < level) return;
|
||||
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
NSString *s = [[NSString alloc] initWithFormat:format arguments:args];
|
||||
NSMutableString *s = [[NSMutableString alloc] initWithFormat:format arguments:args];
|
||||
va_end(args);
|
||||
|
||||
// Only prepend timestamp, severity and binary name if stdout is not a TTY
|
||||
if (isatty(fileno(destination))) {
|
||||
fprintf(destination, "%s\n", [s UTF8String]);
|
||||
} else {
|
||||
NSString *levelName;
|
||||
switch (level) {
|
||||
case LOG_LEVEL_ERROR: levelName = @"E"; break;
|
||||
case LOG_LEVEL_WARN: levelName = @"W"; break;
|
||||
case LOG_LEVEL_INFO: levelName = @"I"; break;
|
||||
case LOG_LEVEL_DEBUG: levelName = @"D"; break;
|
||||
if (useSyslog) {
|
||||
aslclient client = (aslclient)pthread_getspecific(syslogKey);
|
||||
if (client == NULL) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
client = asl_open(NULL, "com.google.santa", 0);
|
||||
asl_set_filter(client, ASL_FILTER_MASK_UPTO(ASL_LEVEL_DEBUG));
|
||||
#pragma clang diagnostic pop
|
||||
pthread_setspecific(syslogKey, client);
|
||||
}
|
||||
|
||||
fprintf(destination, "%s\n", [[NSString stringWithFormat:@"[%@] %@ %@: %@",
|
||||
[dateFormatter stringFromDate:[NSDate date]], levelName, binaryName, s] UTF8String]);
|
||||
char *levelName;
|
||||
int syslogLevel = ASL_LEVEL_DEBUG;
|
||||
switch (level) {
|
||||
case LOG_LEVEL_ERROR:
|
||||
levelName = "E";
|
||||
syslogLevel = ASL_LEVEL_ERR;
|
||||
break;
|
||||
case LOG_LEVEL_WARN:
|
||||
levelName = "W";
|
||||
syslogLevel = ASL_LEVEL_WARNING;
|
||||
break;
|
||||
case LOG_LEVEL_INFO:
|
||||
levelName = "I";
|
||||
syslogLevel = ASL_LEVEL_NOTICE; // Maps to ULS Default
|
||||
break;
|
||||
case LOG_LEVEL_DEBUG:
|
||||
levelName = "D";
|
||||
syslogLevel = ASL_LEVEL_DEBUG;
|
||||
break;
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
asl_log(client, NULL, syslogLevel, "%s %s: %s", levelName, binaryName.UTF8String, s.UTF8String);
|
||||
#pragma clang diagnostic pop
|
||||
} else {
|
||||
[s appendString:@"\n"];
|
||||
size_t len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
|
||||
fwrite([s UTF8String], len, 1, destination);
|
||||
}
|
||||
}
|
||||
|
||||
259
Source/common/SNTPrefixTree.cc
Normal file
259
Source/common/SNTPrefixTree.cc
Normal file
@@ -0,0 +1,259 @@
|
||||
/// Copyright 2018 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include "Source/common/SNTPrefixTree.h"
|
||||
|
||||
#ifdef KERNEL
|
||||
#include <libkern/locks.h>
|
||||
#include "Source/common/SNTLogging.h"
|
||||
|
||||
#else
|
||||
|
||||
#include <mutex>
|
||||
#include <string.h>
|
||||
|
||||
#define LOGD(format, ...) // NOP
|
||||
#define LOGE(format, ...) // NOP
|
||||
|
||||
#define lck_rw_lock_shared(l) pthread_rwlock_rdlock(&l)
|
||||
#define lck_rw_unlock_shared(l) pthread_rwlock_unlock(&l)
|
||||
#define lck_rw_lock_exclusive(l) pthread_rwlock_wrlock(&l)
|
||||
#define lck_rw_unlock_exclusive(l) pthread_rwlock_unlock(&l)
|
||||
|
||||
#define lck_rw_lock_shared_to_exclusive(l) ({ pthread_rwlock_unlock(&l); false; })
|
||||
#define lck_rw_lock_exclusive_to_shared(l) ({ pthread_rwlock_unlock(&l); pthread_rwlock_rdlock(&l); })
|
||||
|
||||
#define lck_mtx_lock(l) l->lock()
|
||||
#define lck_mtx_unlock(l) l->unlock()
|
||||
#endif // KERNEL
|
||||
|
||||
SNTPrefixTree::SNTPrefixTree(uint32_t max_nodes) {
|
||||
root_ = new SantaPrefixNode();
|
||||
node_count_ = 0;
|
||||
max_nodes_ = max_nodes;
|
||||
|
||||
#ifdef KERNEL
|
||||
spt_lock_grp_attr_ = lck_grp_attr_alloc_init();
|
||||
spt_lock_grp_ = lck_grp_alloc_init("santa-prefix-tree-lock", spt_lock_grp_attr_);
|
||||
spt_lock_attr_ = lck_attr_alloc_init();
|
||||
|
||||
spt_lock_ = lck_rw_alloc_init(spt_lock_grp_, spt_lock_attr_);
|
||||
spt_add_lock_ = lck_mtx_alloc_init(spt_lock_grp_, spt_lock_attr_);
|
||||
#else
|
||||
pthread_rwlock_init(&spt_lock_, nullptr);
|
||||
spt_add_lock_ = new std::mutex;
|
||||
#endif
|
||||
}
|
||||
|
||||
IOReturn SNTPrefixTree::AddPrefix(const char *prefix, uint64_t *node_count) {
|
||||
// Serialize requests to AddPrefix. Otherwise one AddPrefix thread could overwrite whole
|
||||
// branches of another. HasPrefix is still free to read the tree, until AddPrefix needs to
|
||||
// modify it.
|
||||
lck_mtx_lock(spt_add_lock_);
|
||||
|
||||
// Don't allow an empty prefix.
|
||||
if (prefix[0] == '\0') return kIOReturnBadArgument;
|
||||
|
||||
LOGD("Trying to add prefix: %s", prefix);
|
||||
|
||||
// Enforce max tree depth.
|
||||
size_t len = strnlen(prefix, max_nodes_);
|
||||
|
||||
// Grab a shared lock until a new branch is required.
|
||||
lck_rw_lock_shared(spt_lock_);
|
||||
|
||||
SantaPrefixNode *node = root_;
|
||||
for (int i = 0; i < len; ++i) {
|
||||
// If there is a node in the path that is considered a prefix, stop adding.
|
||||
// For our purposes we only care about the shortest path that matches.
|
||||
if (node->isPrefix) break;
|
||||
|
||||
// Only process a byte at a time.
|
||||
uint8_t value = prefix[i];
|
||||
|
||||
// Create the child if it does not exist.
|
||||
if (!node->children[value]) {
|
||||
// Upgrade the shared lock.
|
||||
// If the upgrade fails, the shared lock is released.
|
||||
if (!lck_rw_lock_shared_to_exclusive(spt_lock_)) {
|
||||
// Grab a new exclusive lock.
|
||||
lck_rw_lock_exclusive(spt_lock_);
|
||||
}
|
||||
|
||||
// Is there enough room for the rest of the prefix?
|
||||
if ((node_count_ + (len - i)) > max_nodes_) {
|
||||
LOGE("Prefix tree is full, can not add: %s", prefix);
|
||||
|
||||
if (node_count) *node_count = node_count_;
|
||||
lck_rw_unlock_exclusive(spt_lock_);
|
||||
lck_mtx_unlock(spt_add_lock_);
|
||||
return kIOReturnNoResources;
|
||||
}
|
||||
|
||||
// Create the rest of the prefix.
|
||||
while (i < len) {
|
||||
value = prefix[i++];
|
||||
|
||||
SantaPrefixNode *new_node = new SantaPrefixNode();
|
||||
node->children[value] = new_node;
|
||||
++node_count_;
|
||||
|
||||
node = new_node;
|
||||
}
|
||||
|
||||
// This is the end, mark the node as a prefix.
|
||||
LOGD("Added prefix: %s", prefix);
|
||||
|
||||
node->isPrefix = true;
|
||||
|
||||
// Downgrade the exclusive lock
|
||||
lck_rw_lock_exclusive_to_shared(spt_lock_);
|
||||
} else if (i + 1 == len) {
|
||||
// If the child does exist and it is the end...
|
||||
// Set the new, higher prefix and prune the now dead nodes.
|
||||
|
||||
if (!lck_rw_lock_shared_to_exclusive(spt_lock_)) {
|
||||
lck_rw_lock_exclusive(spt_lock_);
|
||||
}
|
||||
|
||||
PruneNode(node->children[value]);
|
||||
|
||||
SantaPrefixNode *new_node = new SantaPrefixNode();
|
||||
new_node->isPrefix = true;
|
||||
|
||||
node->children[value] = new_node;
|
||||
++node_count_;
|
||||
|
||||
LOGD("Added prefix: %s", prefix);
|
||||
|
||||
lck_rw_lock_exclusive_to_shared(spt_lock_);
|
||||
}
|
||||
|
||||
// Get ready for the next iteration.
|
||||
node = node->children[value];
|
||||
}
|
||||
|
||||
if (node_count) *node_count = node_count_;
|
||||
|
||||
lck_rw_unlock_shared(spt_lock_);
|
||||
lck_mtx_unlock(spt_add_lock_);
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
bool SNTPrefixTree::HasPrefix(const char *string) {
|
||||
lck_rw_lock_shared(spt_lock_);
|
||||
|
||||
auto found = false;
|
||||
|
||||
SantaPrefixNode *node = root_;
|
||||
|
||||
// A well formed tree will always break this loop. Even if string doesn't terminate.
|
||||
const char *p = string;
|
||||
while (*p) {
|
||||
// Only process a byte at a time.
|
||||
node = node->children[(uint8_t)*p++];
|
||||
|
||||
// If it doesn't exist in the tree, no match.
|
||||
if (!node) break;
|
||||
|
||||
// If it does exist, is it a prefix?
|
||||
if (node->isPrefix) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
lck_rw_unlock_shared(spt_lock_);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
void SNTPrefixTree::Reset() {
|
||||
lck_rw_lock_exclusive(spt_lock_);
|
||||
|
||||
PruneNode(root_);
|
||||
root_ = new SantaPrefixNode();
|
||||
node_count_ = 0;
|
||||
|
||||
lck_rw_unlock_exclusive(spt_lock_);
|
||||
}
|
||||
|
||||
void SNTPrefixTree::PruneNode(SantaPrefixNode *target) {
|
||||
if (!target) return;
|
||||
|
||||
// For deep trees, a recursive approach will generate too many stack frames. Make a "stack"
|
||||
// and walk the tree.
|
||||
auto stack = new SantaPrefixNode *[node_count_ + 1];
|
||||
if (!stack) {
|
||||
LOGE("Unable to prune tree!");
|
||||
|
||||
return;
|
||||
}
|
||||
auto count = 0;
|
||||
|
||||
// Seed the "stack" with a starting node.
|
||||
stack[count++] = target;
|
||||
|
||||
// Start at the target node and walk the tree to find and delete all the sub-nodes.
|
||||
while (count) {
|
||||
auto node = stack[--count];
|
||||
|
||||
for (int i = 0; i < 256; ++i) {
|
||||
if (!node->children[i]) continue;
|
||||
stack[count++] = node->children[i];
|
||||
}
|
||||
|
||||
delete node;
|
||||
--node_count_;
|
||||
}
|
||||
|
||||
delete[] stack;
|
||||
}
|
||||
|
||||
SNTPrefixTree::~SNTPrefixTree() {
|
||||
lck_rw_lock_exclusive(spt_lock_);
|
||||
PruneNode(root_);
|
||||
root_ = nullptr;
|
||||
lck_rw_unlock_exclusive(spt_lock_);
|
||||
|
||||
#ifdef KERNEL
|
||||
if (spt_lock_) {
|
||||
lck_rw_free(spt_lock_, spt_lock_grp_);
|
||||
spt_lock_ = nullptr;
|
||||
}
|
||||
|
||||
if (spt_add_lock_) {
|
||||
lck_mtx_free(spt_add_lock_, spt_lock_grp_);
|
||||
spt_add_lock_ = nullptr;
|
||||
}
|
||||
|
||||
if (spt_lock_attr_) {
|
||||
lck_attr_free(spt_lock_attr_);
|
||||
spt_lock_attr_ = nullptr;
|
||||
}
|
||||
|
||||
if (spt_lock_grp_) {
|
||||
lck_grp_free(spt_lock_grp_);
|
||||
spt_lock_grp_ = nullptr;
|
||||
}
|
||||
|
||||
if (spt_lock_grp_attr_) {
|
||||
lck_grp_attr_free(spt_lock_grp_attr_);
|
||||
spt_lock_grp_attr_ = nullptr;
|
||||
}
|
||||
#else
|
||||
pthread_rwlock_destroy(&spt_lock_);
|
||||
#endif
|
||||
}
|
||||
103
Source/common/SNTPrefixTree.h
Normal file
103
Source/common/SNTPrefixTree.h
Normal file
@@ -0,0 +1,103 @@
|
||||
/// Copyright 2018 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#ifndef SANTA__SANTA_DRIVER__SANTAPREFIXTREE_H
|
||||
#define SANTA__SANTA_DRIVER__SANTAPREFIXTREE_H
|
||||
|
||||
#include <IOKit/IOReturn.h>
|
||||
#include <sys/param.h>
|
||||
|
||||
#ifdef KERNEL
|
||||
#include <libkern/locks.h>
|
||||
#else
|
||||
// Support for unit testing.
|
||||
#include <mutex>
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#endif // KERNEL
|
||||
|
||||
///
|
||||
/// SantaPrefixTree is a simple prefix tree implementation.
|
||||
/// Operations are thread safe.
|
||||
///
|
||||
class SNTPrefixTree {
|
||||
public:
|
||||
// Add a prefix to the tree.
|
||||
// Optionally pass node_count to get the number of nodes after the add.
|
||||
IOReturn AddPrefix(const char *, uint64_t *node_count = nullptr);
|
||||
|
||||
// Check if the tree has a prefix for string.
|
||||
bool HasPrefix(const char *string);
|
||||
|
||||
// Reset the tree.
|
||||
void Reset();
|
||||
|
||||
SNTPrefixTree(uint32_t max_nodes = kDefaultMaxNodes);
|
||||
~SNTPrefixTree();
|
||||
|
||||
private:
|
||||
///
|
||||
/// SantaPrefixNode is a wrapper class that represents one byte.
|
||||
/// 1 node can represent a whole ASCII character.
|
||||
/// For example a pointer to the 'A' node will be stored at children[0x41].
|
||||
/// It takes 1-4 nodes to represent a UTF-8 encoded Unicode character.
|
||||
///
|
||||
/// The path for "/🤘" would look like this:
|
||||
/// children[0x2f] -> children[0xf0] -> children[0x9f] -> children[0xa4] -> children[0x98]
|
||||
///
|
||||
/// The path for "/dev" is:
|
||||
/// children[0x2f] -> children[0x64] -> children[0x65] -> children[0x76]
|
||||
///
|
||||
/// Lookups of children are O(1).
|
||||
///
|
||||
/// Having the nodes represented by a smaller width, such as a nibble (1/2 byte), would
|
||||
/// drastically decrease the memory footprint but would double required dereferences.
|
||||
///
|
||||
/// TODO(bur): Potentially convert this into a full on radix tree.
|
||||
///
|
||||
class SantaPrefixNode {
|
||||
public:
|
||||
bool isPrefix;
|
||||
SantaPrefixNode *children[256];
|
||||
};
|
||||
|
||||
// PruneNode will remove the passed in node from the tree.
|
||||
// The passed in node and all subnodes will be deleted.
|
||||
// It is the caller's responsibility to reset the pointer to this node (held by the parent).
|
||||
// If the tree is in use grab the exclusive lock.
|
||||
void PruneNode(SantaPrefixNode *);
|
||||
|
||||
SantaPrefixNode *root_;
|
||||
|
||||
// Each node takes up ~2k, assuming MAXPATHLEN is 1024 max out at ~2MB.
|
||||
static const uint32_t kDefaultMaxNodes = MAXPATHLEN;
|
||||
uint32_t max_nodes_;
|
||||
uint32_t node_count_;
|
||||
|
||||
#ifdef KERNEL
|
||||
lck_grp_t *spt_lock_grp_;
|
||||
lck_grp_attr_t *spt_lock_grp_attr_;
|
||||
lck_attr_t *spt_lock_attr_;
|
||||
lck_rw_t *spt_lock_;
|
||||
lck_mtx_t *spt_add_lock_;
|
||||
#else // KERNEL
|
||||
void *spt_lock_grp_;
|
||||
void *spt_lock_grp_attr_;
|
||||
void *spt_lock_attr_;
|
||||
pthread_rwlock_t spt_lock_;
|
||||
std::mutex *spt_add_lock_;
|
||||
#endif // KERNEL
|
||||
};
|
||||
|
||||
#endif /* SANTA__SANTA_DRIVER__SANTAPREFIXTREE_H */
|
||||
70
Source/common/SNTPrefixTreeTest.mm
Normal file
70
Source/common/SNTPrefixTreeTest.mm
Normal file
@@ -0,0 +1,70 @@
|
||||
/// Copyright 2018 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include "Source/common/SNTPrefixTree.h"
|
||||
|
||||
@interface SNTPrefixTreeTest : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation SNTPrefixTreeTest
|
||||
|
||||
- (void)testAddAndHas {
|
||||
auto t = SNTPrefixTree();
|
||||
XCTAssertFalse(t.HasPrefix("/private/var/tmp/file1"));
|
||||
t.AddPrefix("/private/var/tmp/");
|
||||
XCTAssertTrue(t.HasPrefix("/private/var/tmp/file1"));
|
||||
}
|
||||
|
||||
- (void)testReset {
|
||||
auto t = SNTPrefixTree();
|
||||
t.AddPrefix("/private/var/tmp/");
|
||||
XCTAssertTrue(t.HasPrefix("/private/var/tmp/file1"));
|
||||
t.Reset();
|
||||
XCTAssertFalse(t.HasPrefix("/private/var/tmp/file1"));
|
||||
}
|
||||
|
||||
- (void)testThreading {
|
||||
uint32_t count = 4096;
|
||||
auto t = new SNTPrefixTree(count * (uint32_t)[NSUUID UUID].UUIDString.length);
|
||||
|
||||
NSMutableArray *UUIDs = [NSMutableArray arrayWithCapacity:count];
|
||||
for (int i = 0; i < count; ++i) {
|
||||
[UUIDs addObject:[NSUUID UUID].UUIDString];
|
||||
}
|
||||
|
||||
// Create a bunch of background noise.
|
||||
dispatch_async(dispatch_get_global_queue(0, 0), ^{
|
||||
dispatch_apply(UINT64_MAX, dispatch_get_global_queue(0, 0), ^(size_t i) {
|
||||
t->HasPrefix([UUIDs[i % count] UTF8String]);
|
||||
});
|
||||
});
|
||||
|
||||
// Fill up the tree.
|
||||
dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t i) {
|
||||
if (t->AddPrefix([UUIDs[i] UTF8String]) != kIOReturnSuccess) {
|
||||
XCTFail();
|
||||
}
|
||||
});
|
||||
|
||||
// Make sure every leaf byte is found.
|
||||
dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t i) {
|
||||
if (!t->HasPrefix([UUIDs[i] UTF8String])) {
|
||||
XCTFail();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -12,7 +12,9 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include "SNTCommonEnums.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
|
||||
///
|
||||
/// Represents a Rule.
|
||||
@@ -22,29 +24,49 @@
|
||||
///
|
||||
/// The hash of the object this rule is for
|
||||
///
|
||||
@property NSString *shasum;
|
||||
@property(copy) NSString *shasum;
|
||||
|
||||
///
|
||||
/// The state of this rule
|
||||
///
|
||||
@property santa_rulestate_t state;
|
||||
@property SNTRuleState state;
|
||||
|
||||
///
|
||||
/// The type of object this rule is for (binary, certificate)
|
||||
///
|
||||
@property santa_ruletype_t type;
|
||||
@property SNTRuleType type;
|
||||
|
||||
///
|
||||
/// A custom message that will be displayed if this rule blocks a binary from executing
|
||||
///
|
||||
@property NSString *customMsg;
|
||||
@property(copy) NSString *customMsg;
|
||||
|
||||
///
|
||||
/// The time when this rule was last retrieved from the rules database, if rule is transitive.
|
||||
/// Stored as number of seconds since 00:00:00 UTC on 1 January 2001.
|
||||
///
|
||||
@property(readonly) NSUInteger timestamp;
|
||||
|
||||
///
|
||||
/// Designated initializer.
|
||||
///
|
||||
- (instancetype)initWithShasum:(NSString *)shasum
|
||||
state:(santa_rulestate_t)state
|
||||
type:(santa_ruletype_t)type
|
||||
state:(SNTRuleState)state
|
||||
type:(SNTRuleType)type
|
||||
customMsg:(NSString *)customMsg
|
||||
timestamp:(NSUInteger)timestamp;
|
||||
|
||||
///
|
||||
/// Initialize with a default timestamp: current time if rule state is transitive, 0 otherwise.
|
||||
///
|
||||
- (instancetype)initWithShasum:(NSString *)shasum
|
||||
state:(SNTRuleState)state
|
||||
type:(SNTRuleType)type
|
||||
customMsg:(NSString *)customMsg;
|
||||
|
||||
///
|
||||
/// Sets timestamp of rule to the current time.
|
||||
///
|
||||
- (void)resetTimestamp;
|
||||
|
||||
@end
|
||||
|
||||
@@ -12,26 +12,51 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTRule.h"
|
||||
#import "Source/common/SNTRule.h"
|
||||
|
||||
@interface SNTRule()
|
||||
@property(readwrite) NSUInteger timestamp;
|
||||
@end
|
||||
|
||||
@implementation SNTRule
|
||||
|
||||
- (instancetype)initWithShasum:(NSString *)shasum
|
||||
state:(santa_rulestate_t)state
|
||||
type:(santa_ruletype_t)type
|
||||
customMsg:(NSString *)customMsg {
|
||||
state:(SNTRuleState)state
|
||||
type:(SNTRuleType)type
|
||||
customMsg:(NSString *)customMsg
|
||||
timestamp:(NSUInteger)timestamp {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_shasum = shasum;
|
||||
_state = state;
|
||||
_type = type;
|
||||
_customMsg = customMsg;
|
||||
_timestamp = timestamp;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithShasum:(NSString *)shasum
|
||||
state:(SNTRuleState)state
|
||||
type:(SNTRuleType)type
|
||||
customMsg:(NSString *)customMsg {
|
||||
self = [self initWithShasum:shasum
|
||||
state:state
|
||||
type:type
|
||||
customMsg:customMsg
|
||||
timestamp:0];
|
||||
// Initialize timestamp to current time if rule is transitive.
|
||||
if (self && state == SNTRuleStateWhitelistTransitive) {
|
||||
[self resetTimestamp];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark NSSecureCoding
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wobjc-literal-conversion"
|
||||
#define ENCODE(obj, key) if (obj) [coder encodeObject:obj forKey:key]
|
||||
#define DECODE(cls, key) [decoder decodeObjectOfClass:[cls class] forKey:key]
|
||||
|
||||
@@ -44,6 +69,7 @@
|
||||
ENCODE(@(self.state), @"state");
|
||||
ENCODE(@(self.type), @"type");
|
||||
ENCODE(self.customMsg, @"custommsg");
|
||||
ENCODE(@(self.timestamp), @"timestamp");
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||
@@ -53,11 +79,40 @@
|
||||
_state = [DECODE(NSNumber, @"state") intValue];
|
||||
_type = [DECODE(NSNumber, @"type") intValue];
|
||||
_customMsg = DECODE(NSString, @"custommsg");
|
||||
_timestamp = [DECODE(NSNumber, @"timestamp") unsignedIntegerValue];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#undef DECODE
|
||||
#undef ENCODE
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
- (BOOL)isEqual:(id)other {
|
||||
if (other == self) return YES;
|
||||
if (![other isKindOfClass:[SNTRule class]]) return NO;
|
||||
SNTRule *o = other;
|
||||
return ([self.shasum isEqual:o.shasum] && self.state == o.state && self.type == o.type);
|
||||
}
|
||||
|
||||
- (NSUInteger)hash {
|
||||
NSUInteger prime = 31;
|
||||
NSUInteger result = 1;
|
||||
result = prime * result + [self.shasum hash];
|
||||
result = prime * result + self.state;
|
||||
result = prime * result + self.type;
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"SNTRule: SHA-256: %@, State: %ld, Type: %ld, Timestamp: %lu",
|
||||
self.shasum, self.state, self.type, (unsigned long)self.timestamp];
|
||||
}
|
||||
|
||||
# pragma mark Last-access Timestamp
|
||||
|
||||
- (void)resetTimestamp {
|
||||
self.timestamp = (NSUInteger)[[NSDate date] timeIntervalSinceReferenceDate];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include "SNTCommonEnums.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
|
||||
///
|
||||
/// Represents an event stored in the database.
|
||||
@@ -20,7 +22,7 @@
|
||||
@interface SNTStoredEvent : NSObject<NSSecureCoding>
|
||||
|
||||
///
|
||||
/// An index for this event, empty unless the event came from the database.
|
||||
/// An index for this event, randomly generated during initialization.
|
||||
///
|
||||
@property NSNumber *idx;
|
||||
|
||||
@@ -35,10 +37,43 @@
|
||||
@property NSString *filePath;
|
||||
|
||||
///
|
||||
/// If the executed file was part of the bundle, this is the CFBundleName.
|
||||
/// Set to YES if the event is a part of a bundle. When an event is passed to SantaGUI this propery
|
||||
/// will be used as an indicator to to kick off bundle hashing as necessary. Default value is NO.
|
||||
///
|
||||
@property BOOL needsBundleHash;
|
||||
|
||||
///
|
||||
/// If the executed file was part of a bundle, this is the calculated hash of all the nested
|
||||
/// executables within the bundle.
|
||||
///
|
||||
@property NSString *fileBundleHash;
|
||||
|
||||
///
|
||||
/// If the executed file was part of a bundle, this is the time in ms it took to hash the bundle.
|
||||
///
|
||||
@property NSNumber *fileBundleHashMilliseconds;
|
||||
|
||||
///
|
||||
/// If the executed file was part of a bundle, this is the total count of related mach-o binaries.
|
||||
///
|
||||
@property NSNumber *fileBundleBinaryCount;
|
||||
|
||||
///
|
||||
/// If the executed file was part of the bundle, this is the CFBundleDisplayName, if it exists
|
||||
/// or the CFBundleName if not.
|
||||
///
|
||||
@property NSString *fileBundleName;
|
||||
|
||||
///
|
||||
/// If the executed file was part of the bundle, this is the path to the bundle.
|
||||
///
|
||||
@property NSString *fileBundlePath;
|
||||
|
||||
///
|
||||
/// The relative path to the bundle's main executable.
|
||||
///
|
||||
@property NSString *fileBundleExecutableRelPath;
|
||||
|
||||
///
|
||||
/// If the executed file was part of the bundle, this is the CFBundleID.
|
||||
///
|
||||
@@ -55,7 +90,7 @@
|
||||
@property NSString *fileBundleVersionString;
|
||||
|
||||
///
|
||||
/// If the executed file was signed, this is an NSArray of SNTCertificate's
|
||||
/// If the executed file was signed, this is an NSArray of MOLCertificate's
|
||||
/// representing the signing chain.
|
||||
///
|
||||
@property NSArray *signingChain;
|
||||
@@ -73,7 +108,7 @@
|
||||
///
|
||||
/// The decision santad returned.
|
||||
///
|
||||
@property santa_eventstate_t decision;
|
||||
@property SNTEventState decision;
|
||||
|
||||
///
|
||||
/// NSArray of logged in users when the decision was made.
|
||||
@@ -100,4 +135,12 @@
|
||||
///
|
||||
@property NSString *parentName;
|
||||
|
||||
///
|
||||
/// Quarantine data about the executed file, if any.
|
||||
///
|
||||
@property NSString *quarantineDataURL;
|
||||
@property NSString *quarantineRefererURL;
|
||||
@property NSDate *quarantineTimestamp;
|
||||
@property NSString *quarantineAgentBundleID;
|
||||
|
||||
@end
|
||||
|
||||
@@ -12,17 +12,20 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTStoredEvent.h"
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
|
||||
#import "SNTCertificate.h"
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
|
||||
@implementation SNTStoredEvent
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wobjc-literal-conversion"
|
||||
|
||||
#define ENCODE(obj, key) if (obj) [coder encodeObject:obj forKey:key]
|
||||
#define DECODE(cls, key) [decoder decodeObjectOfClass:[cls class] forKey:key]
|
||||
#define DECODEARRAY(cls, key) \
|
||||
[decoder decodeObjectOfClasses:[NSSet setWithObjects:[NSArray class], [cls class], nil] \
|
||||
forKey:key]
|
||||
forKey:key]
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
@@ -33,7 +36,13 @@
|
||||
ENCODE(self.fileSHA256, @"fileSHA256");
|
||||
ENCODE(self.filePath, @"filePath");
|
||||
|
||||
ENCODE(@(self.needsBundleHash), @"needsBundleHash");
|
||||
ENCODE(self.fileBundleHash, @"fileBundleHash");
|
||||
ENCODE(self.fileBundleHashMilliseconds, @"fileBundleHashMilliseconds");
|
||||
ENCODE(self.fileBundleBinaryCount, @"fileBundleBinaryCount");
|
||||
ENCODE(self.fileBundleName, @"fileBundleName");
|
||||
ENCODE(self.fileBundlePath, @"fileBundlePath");
|
||||
ENCODE(self.fileBundleExecutableRelPath, @"fileBundleExecutableRelPath");
|
||||
ENCODE(self.fileBundleID, @"fileBundleID");
|
||||
ENCODE(self.fileBundleVersion, @"fileBundleVersion");
|
||||
ENCODE(self.fileBundleVersionString, @"fileBundleVersionString");
|
||||
@@ -49,6 +58,19 @@
|
||||
|
||||
ENCODE(self.loggedInUsers, @"loggedInUsers");
|
||||
ENCODE(self.currentSessions, @"currentSessions");
|
||||
|
||||
ENCODE(self.quarantineDataURL, @"quarantineDataURL");
|
||||
ENCODE(self.quarantineRefererURL, @"quarantineRefererURL");
|
||||
ENCODE(self.quarantineTimestamp, @"quarantineTimestamp");
|
||||
ENCODE(self.quarantineAgentBundleID, @"quarantineAgentBundleID");
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_idx = @(arc4random());
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||
@@ -58,22 +80,33 @@
|
||||
_fileSHA256 = DECODE(NSString, @"fileSHA256");
|
||||
_filePath = DECODE(NSString, @"filePath");
|
||||
|
||||
_needsBundleHash = [DECODE(NSNumber, @"needsBundleHash") boolValue];
|
||||
_fileBundleHash = DECODE(NSString, @"fileBundleHash");
|
||||
_fileBundleHashMilliseconds = DECODE(NSNumber, @"fileBundleHashMilliseconds");
|
||||
_fileBundleBinaryCount = DECODE(NSNumber, @"fileBundleBinaryCount");
|
||||
_fileBundleName = DECODE(NSString, @"fileBundleName");
|
||||
_fileBundlePath = DECODE(NSString, @"fileBundlePath");
|
||||
_fileBundleExecutableRelPath = DECODE(NSString, @"fileBundleExecutableRelPath");
|
||||
_fileBundleID = DECODE(NSString, @"fileBundleID");
|
||||
_fileBundleVersion = DECODE(NSString, @"fileBundleVersion");
|
||||
_fileBundleVersionString = DECODE(NSString, @"fileBundleVersionString");
|
||||
|
||||
_signingChain = DECODEARRAY(SNTCertificate, @"signingChain");
|
||||
_signingChain = DECODEARRAY(MOLCertificate, @"signingChain");
|
||||
|
||||
_executingUser = DECODE(NSString, @"executingUser");
|
||||
_occurrenceDate = DECODE(NSDate, @"occurrenceDate");
|
||||
_decision = (santa_eventstate_t)[DECODE(NSNumber, @"decision") intValue];
|
||||
_decision = (SNTEventState)[DECODE(NSNumber, @"decision") intValue];
|
||||
_pid = DECODE(NSNumber, @"pid");
|
||||
_ppid = DECODE(NSNumber, @"ppid");
|
||||
_parentName = DECODE(NSString, @"parentName");
|
||||
|
||||
_loggedInUsers = DECODEARRAY(NSString, @"loggedInUsers");
|
||||
_currentSessions = DECODEARRAY(NSString, @"currentSessions");
|
||||
|
||||
_quarantineDataURL = DECODE(NSString, @"quarantineDataURL");
|
||||
_quarantineRefererURL = DECODE(NSString, @"quarantineRefererURL");
|
||||
_quarantineTimestamp = DECODE(NSDate, @"quarantineTimestamp");
|
||||
_quarantineAgentBundleID = DECODE(NSString, @"quarantineAgentBundleID");
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -81,7 +114,7 @@
|
||||
- (BOOL)isEqual:(id)other {
|
||||
if (other == self) return YES;
|
||||
if (![other isKindOfClass:[SNTStoredEvent class]]) return NO;
|
||||
SNTStoredEvent *o;
|
||||
SNTStoredEvent *o = other;
|
||||
return ([self.fileSHA256 isEqual:o.fileSHA256] && [self.idx isEqual:o.idx]);
|
||||
}
|
||||
|
||||
@@ -99,4 +132,6 @@
|
||||
[NSString stringWithFormat:@"SNTStoredEvent[%@] with SHA-256: %@", self.idx, self.fileSHA256];
|
||||
}
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
@end
|
||||
|
||||
22
Source/common/SNTStrengthify.h
Normal file
22
Source/common/SNTStrengthify.h
Normal file
@@ -0,0 +1,22 @@
|
||||
/// Copyright 2016 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#define STRONGIFY(var) \
|
||||
_Pragma("clang diagnostic push") \
|
||||
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
|
||||
__strong __typeof(var) var = (Weak_##var); \
|
||||
_Pragma("clang diagnostic pop")
|
||||
|
||||
#define WEAKIFY(var) \
|
||||
__weak __typeof(var) Weak_##var = (var);
|
||||
@@ -12,6 +12,8 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
///
|
||||
/// Simple class for fetching system information
|
||||
///
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTSystemInfo.h"
|
||||
#import "Source/common/SNTSystemInfo.h"
|
||||
|
||||
@implementation SNTSystemInfo
|
||||
|
||||
|
||||
70
Source/common/SNTXPCBundleServiceInterface.h
Normal file
70
Source/common/SNTXPCBundleServiceInterface.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/// Copyright 2017 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
@class SNTStoredEvent;
|
||||
|
||||
/// A block that takes the calculated bundle hash, associated events and hashing time in ms.
|
||||
typedef void (^SNTBundleHashBlock)(NSString *, NSArray<SNTStoredEvent *> *, NSNumber *);
|
||||
|
||||
/// Protocol implemented by santabs and utilized by SantaGUI for bundle hashing
|
||||
@protocol SNTBundleServiceXPC
|
||||
|
||||
///
|
||||
/// @param listener The listener to connect back to the SantaGUI.
|
||||
///
|
||||
- (void)setNotificationListener:(NSXPCListenerEndpoint *)listener;
|
||||
|
||||
///
|
||||
/// Hash a bundle for an event. The SNTBundleHashBlock will be called with nil parameters if a
|
||||
/// failure or cancellation occurs.
|
||||
///
|
||||
/// @param event The event that includes the fileBundlePath to be hashed. This method will
|
||||
/// attempt to to find and use the ancestor bundle as a starting point.
|
||||
/// @param reply A SNTBundleHashBlock to be executed upon completion or cancellation.
|
||||
///
|
||||
/// @note If there is a current NSProgress when called this method will report back its progress.
|
||||
///
|
||||
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event reply:(SNTBundleHashBlock)reply;
|
||||
|
||||
///
|
||||
/// santabundleservice is launched on demand by launchd, call spindown to let santabundleservice know you are done with it.
|
||||
///
|
||||
- (void)spindown;
|
||||
|
||||
@end
|
||||
|
||||
@interface SNTXPCBundleServiceInterface : NSObject
|
||||
|
||||
///
|
||||
/// Returns an initialized NSXPCInterface for the SNTBundleServiceXPC protocol.
|
||||
/// Ensures any methods that accept custom classes as arguments are set-up before returning.
|
||||
///
|
||||
+ (NSXPCInterface *)bundleServiceInterface;
|
||||
|
||||
///
|
||||
/// Returns the MachService ID for this service.
|
||||
///
|
||||
+ (NSString *)serviceID;
|
||||
|
||||
///
|
||||
/// Retrieve a pre-configured MOLXPCConnection for communicating with santabundleservice.
|
||||
/// Connections just needs any handlers set and then can be resumed and used.
|
||||
///
|
||||
+ (MOLXPCConnection *)configuredConnection;
|
||||
|
||||
@end
|
||||
43
Source/common/SNTXPCBundleServiceInterface.m
Normal file
43
Source/common/SNTXPCBundleServiceInterface.m
Normal file
@@ -0,0 +1,43 @@
|
||||
/// Copyright 2017 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/common/SNTXPCBundleServiceInterface.h"
|
||||
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
|
||||
@implementation SNTXPCBundleServiceInterface
|
||||
|
||||
+ (NSXPCInterface *)bundleServiceInterface {
|
||||
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTBundleServiceXPC)];
|
||||
|
||||
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
|
||||
forSelector:@selector(hashBundleBinariesForEvent:reply:)
|
||||
argumentIndex:1
|
||||
ofReply:YES];
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
+ (NSString *)serviceID {
|
||||
return @"com.google.santa.bundleservice";
|
||||
}
|
||||
|
||||
+ (MOLXPCConnection *)configuredConnection {
|
||||
MOLXPCConnection *c = [[MOLXPCConnection alloc] initClientWithName:[self serviceID]
|
||||
privileged:YES];
|
||||
c.remoteInterface = [self bundleServiceInterface];
|
||||
return c;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,119 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
///
|
||||
/// A validating XPC connection/listener which uses codesigning to validate that both ends of the
|
||||
/// connection were signed by the same certificate chain.
|
||||
///
|
||||
/// Example server started by @c launchd where the @c launchd job has a @c MachServices key:
|
||||
///
|
||||
/// @code
|
||||
/// SNTXPCConnection *conn = [[SNTXPCConnection alloc] initServerWithName:@"MyServer"];
|
||||
/// conn.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MyServerProtocol)];
|
||||
/// conn.exportedObject = myObject;
|
||||
/// conn.remoteInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MyClientProtocol)];
|
||||
/// [conn resume];
|
||||
/// @endcode
|
||||
///
|
||||
/// Example client, connecting to above server:
|
||||
///
|
||||
/// @code
|
||||
/// SNTXPCConnection *conn = [[SNTXPCConnection alloc] initClientWithName:"MyServer"
|
||||
/// withOptions:0];
|
||||
/// conn.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MyClientProtocol)];
|
||||
/// conn.exportedObject = myObject;
|
||||
/// conn.remoteInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MyServerProtocol)];
|
||||
/// conn.invalidationHandler = ^{ NSLog(@"Connection invalidated") };
|
||||
/// [conn resume];
|
||||
/// @endcode
|
||||
///
|
||||
/// Either side can then send a message to the other with:
|
||||
///
|
||||
/// @code
|
||||
/// [conn.remoteObjectProxy selectorInRemoteInterface];
|
||||
/// @endcode
|
||||
///
|
||||
/// @note messages are always delivered on a background thread!
|
||||
///
|
||||
@interface SNTXPCConnection : NSObject<NSXPCListenerDelegate>
|
||||
|
||||
typedef void (^SNTXPCInvalidationBlock)(void);
|
||||
typedef void (^SNTXPCAcceptedBlock)(void);
|
||||
typedef void (^SNTXPCRejectedBlock)(void);
|
||||
|
||||
///
|
||||
/// The interface the remote object should conform to.
|
||||
///
|
||||
@property(retain) NSXPCInterface *remoteInterface;
|
||||
|
||||
///
|
||||
/// A proxy to the object at the other end of the connection.
|
||||
///
|
||||
/// @warning Do not send a message to this object if you didn't set @c remoteInterface above
|
||||
/// before calling the @c resume method. Doing so will throw an exception.
|
||||
///
|
||||
@property(readonly, nonatomic) id remoteObjectProxy;
|
||||
|
||||
///
|
||||
/// The interface this object exports.
|
||||
///
|
||||
@property(retain) NSXPCInterface *exportedInterface;
|
||||
|
||||
///
|
||||
/// The object that responds to messages from the other end.
|
||||
///
|
||||
@property(retain) id exportedObject;
|
||||
|
||||
///
|
||||
/// A block to run when the connection is invalidated.
|
||||
///
|
||||
@property(copy) SNTXPCInvalidationBlock invalidationHandler;
|
||||
|
||||
///
|
||||
/// A block to run when the connection has been accepted.
|
||||
///
|
||||
@property(copy) SNTXPCAcceptedBlock acceptedHandler;
|
||||
|
||||
///
|
||||
/// A block to run when the connection has been rejected.
|
||||
///
|
||||
@property(copy) SNTXPCRejectedBlock rejectedHandler;
|
||||
|
||||
///
|
||||
/// Initializer for the 'server' side of the connection, the binary that was started by launchd.
|
||||
///
|
||||
/// @param name MachService name
|
||||
///
|
||||
- (instancetype)initServerWithName:(NSString *)name;
|
||||
|
||||
///
|
||||
/// Initializer for the 'client' side of the connection.
|
||||
///
|
||||
/// @param name MachService name
|
||||
/// @param options Use NSXPCConnectionPrivileged if the server is running as root, otherwise use 0.
|
||||
///
|
||||
- (instancetype)initClientWithName:(NSString *)name options:(NSXPCConnectionOptions)options;
|
||||
|
||||
///
|
||||
/// Call when the properties of the object have been set-up and you're ready for connections.
|
||||
/// Blocks the executing thread for up to 5s while waiting for the verification to complete.
|
||||
///
|
||||
- (void)resume;
|
||||
|
||||
///
|
||||
/// Invalidate the connection. This must be done before the connection can be released.
|
||||
///
|
||||
- (void)invalidate;
|
||||
|
||||
@end
|
||||
@@ -1,220 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTXPCConnection.h"
|
||||
|
||||
#import "SNTCodesignChecker.h"
|
||||
|
||||
@protocol XPCConnectionValidityRequest
|
||||
- (void)isConnectionValidWithBlock:(void (^)(BOOL))block;
|
||||
@end
|
||||
|
||||
@interface SNTXPCConnection ()
|
||||
|
||||
///
|
||||
/// The XPC listener (used on server-side only).
|
||||
///
|
||||
@property NSXPCListener *listenerObject;
|
||||
|
||||
///
|
||||
/// The current connection object.
|
||||
///
|
||||
@property NSXPCConnection *currentConnection;
|
||||
|
||||
///
|
||||
/// The remote interface to use while the connection hasn't been validated.
|
||||
///
|
||||
@property NSXPCInterface *validatorInterface;
|
||||
|
||||
@end
|
||||
|
||||
@implementation SNTXPCConnection
|
||||
|
||||
#pragma mark Initializers
|
||||
- (instancetype)initServerWithName:(NSString *)name {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
Protocol *validatorProtocol = @protocol(XPCConnectionValidityRequest);
|
||||
_validatorInterface = [NSXPCInterface interfaceWithProtocol:validatorProtocol];
|
||||
_listenerObject = [[NSXPCListener alloc] initWithMachServiceName:name];
|
||||
|
||||
if (!_validatorInterface || !_listenerObject) return nil;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initClientWithName:(NSString *)name options:(NSXPCConnectionOptions)options {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
Protocol *validatorProtocol = @protocol(XPCConnectionValidityRequest);
|
||||
_validatorInterface = [NSXPCInterface interfaceWithProtocol:validatorProtocol];
|
||||
_currentConnection = [[NSXPCConnection alloc] initWithMachServiceName:name options:options];
|
||||
|
||||
if (!_validatorInterface || !_currentConnection) return nil;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
return nil;
|
||||
}
|
||||
|
||||
#pragma mark Connection set-up
|
||||
|
||||
- (void)resume {
|
||||
if (self.listenerObject) {
|
||||
// A new listener doesn't do anything until a client connects.
|
||||
self.listenerObject.delegate = self;
|
||||
[self.listenerObject resume];
|
||||
} else {
|
||||
// A new client begins the validation process.
|
||||
NSXPCConnection *connection = self.currentConnection;
|
||||
|
||||
connection.remoteObjectInterface = self.validatorInterface;
|
||||
|
||||
connection.invalidationHandler = ^{
|
||||
[self invokeInvalidationHandler];
|
||||
self.currentConnection = nil;
|
||||
};
|
||||
|
||||
connection.interruptionHandler = ^{ [self.currentConnection invalidate]; };
|
||||
|
||||
[connection resume];
|
||||
|
||||
__block BOOL verificationComplete = NO;
|
||||
[[connection remoteObjectProxy] isConnectionValidWithBlock:^void(BOOL response) {
|
||||
pid_t pid = self.currentConnection.processIdentifier;
|
||||
|
||||
SNTCodesignChecker *selfCS = [[SNTCodesignChecker alloc] initWithSelf];
|
||||
SNTCodesignChecker *otherCS = [[SNTCodesignChecker alloc] initWithPID:pid];
|
||||
|
||||
if (response && [otherCS signingInformationMatches:selfCS]) {
|
||||
[self.currentConnection suspend];
|
||||
self.currentConnection.remoteObjectInterface = self.remoteInterface;
|
||||
self.currentConnection.exportedInterface = self.exportedInterface;
|
||||
self.currentConnection.exportedObject = self.exportedObject;
|
||||
[self invokeAcceptedHandler];
|
||||
[self.currentConnection resume];
|
||||
verificationComplete = YES;
|
||||
} else {
|
||||
[self invokeRejectedHandler];
|
||||
[self.currentConnection invalidate];
|
||||
self.currentConnection = nil;
|
||||
verificationComplete = YES;
|
||||
}
|
||||
}];
|
||||
|
||||
// Wait for validation to complete, at most 5s
|
||||
for (int sleepLoops = 0; sleepLoops < 1000 && !verificationComplete; sleepLoops++) {
|
||||
usleep(5000);
|
||||
}
|
||||
if (!verificationComplete) [self invalidate];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)connection {
|
||||
// Reject connection if a connection already exists. As the invalidation/interruption handlers
|
||||
// both cause the currentConnection to be nil'd out, this should be OK.
|
||||
if (self.currentConnection) return NO;
|
||||
|
||||
connection.exportedObject = self;
|
||||
connection.exportedInterface = self.validatorInterface;
|
||||
|
||||
connection.invalidationHandler = ^{
|
||||
[self invokeInvalidationHandler];
|
||||
self.currentConnection = nil;
|
||||
};
|
||||
|
||||
connection.interruptionHandler = ^{
|
||||
// Invalidate the connection, causing the handler above to run
|
||||
[self.currentConnection invalidate];
|
||||
};
|
||||
|
||||
// At this point the client is connected and can send messages but the only message it can send
|
||||
// is isConnectionValidWithBlock: and we won't send anything to it until it has.
|
||||
self.currentConnection = connection;
|
||||
|
||||
[connection resume];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)isConnectionValidWithBlock:(void (^)(BOOL))block {
|
||||
pid_t pid = self.currentConnection.processIdentifier;
|
||||
|
||||
SNTCodesignChecker *selfCS = [[SNTCodesignChecker alloc] initWithSelf];
|
||||
SNTCodesignChecker *otherCS = [[SNTCodesignChecker alloc] initWithPID:pid];
|
||||
|
||||
if ([otherCS signingInformationMatches:selfCS]) {
|
||||
[self.currentConnection suspend];
|
||||
self.currentConnection.remoteObjectInterface = self.remoteInterface;
|
||||
self.currentConnection.exportedInterface = self.exportedInterface;
|
||||
self.currentConnection.exportedObject = self.exportedObject;
|
||||
[self.currentConnection resume];
|
||||
|
||||
[self invokeAcceptedHandler];
|
||||
|
||||
// Let remote end know that we accepted. In acception this must come last otherwise
|
||||
// the remote end might start sending messages before the interface is fully set-up.
|
||||
block(YES);
|
||||
} else {
|
||||
// Let remote end know that we rejected. In rejection this must come first otherwise
|
||||
// the connection is invalidated before the client ever realizes.
|
||||
block(NO);
|
||||
|
||||
[self invokeRejectedHandler];
|
||||
|
||||
[self.currentConnection invalidate];
|
||||
self.currentConnection = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (id)remoteObjectProxy {
|
||||
if (self.currentConnection && self.currentConnection.remoteObjectInterface) {
|
||||
return [self.currentConnection remoteObjectProxyWithErrorHandler:^(NSError *error) {
|
||||
[self.currentConnection invalidate];
|
||||
}];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)invokeAcceptedHandler {
|
||||
if (self.acceptedHandler) {
|
||||
self.acceptedHandler();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)invokeRejectedHandler {
|
||||
if (self.rejectedHandler) {
|
||||
self.rejectedHandler();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)invokeInvalidationHandler {
|
||||
if (self.invalidationHandler) {
|
||||
self.invalidationHandler();
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark Connection tear-down
|
||||
|
||||
- (void)invalidate {
|
||||
if (self.currentConnection) {
|
||||
[self.currentConnection invalidate];
|
||||
self.currentConnection = nil;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -12,39 +12,48 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include "SNTCommonEnums.h"
|
||||
|
||||
@class SNTRule;
|
||||
@class SNTStoredEvent;
|
||||
#import "Source/common/SNTXPCUnprivilegedControlInterface.h"
|
||||
|
||||
///
|
||||
/// Protocol implemented by santad and utilized by santactl
|
||||
/// Protocol implemented by santad and utilized by santactl (privileged operations)
|
||||
///
|
||||
@protocol SNTDaemonControlXPC
|
||||
@protocol SNTDaemonControlXPC <SNTUnprivilegedDaemonControlXPC>
|
||||
|
||||
///
|
||||
/// Kernel ops
|
||||
///
|
||||
- (void)cacheCount:(void (^)(int64_t))reply;
|
||||
- (void)flushCache:(void (^)(BOOL))reply;
|
||||
|
||||
///
|
||||
/// Database ops
|
||||
///
|
||||
- (void)databaseRuleCounts:(void (^)(int64_t binary, int64_t certificate))reply;
|
||||
- (void)databaseRuleAddRule:(SNTRule *)rule cleanSlate:(BOOL)cleanSlate reply:(void (^)())reply;
|
||||
- (void)databaseRuleAddRules:(NSArray *)rules cleanSlate:(BOOL)cleanSlate reply:(void (^)())reply;
|
||||
|
||||
- (void)databaseEventCount:(void (^)(int64_t count))reply;
|
||||
- (void)databaseEventForSHA256:(NSString *)sha256 reply:(void (^)(SNTStoredEvent *))reply;
|
||||
- (void)databaseRuleAddRules:(NSArray *)rules
|
||||
cleanSlate:(BOOL)cleanSlate
|
||||
reply:(void (^)(NSError *error))reply;
|
||||
- (void)databaseEventsPending:(void (^)(NSArray *events))reply;
|
||||
- (void)databaseRemoveEventsWithIDs:(NSArray *)ids;
|
||||
- (void)databaseRuleForBinarySHA256:(NSString *)binarySHA256
|
||||
certificateSHA256:(NSString *)certificateSHA256
|
||||
reply:(void (^)(SNTRule *))reply;
|
||||
|
||||
///
|
||||
/// Misc ops
|
||||
/// Config ops
|
||||
///
|
||||
- (void)clientMode:(void (^)(santa_clientmode_t))reply;
|
||||
- (void)setClientMode:(santa_clientmode_t)mode reply:(void (^)())reply;
|
||||
- (void)setClientMode:(SNTClientMode)mode reply:(void (^)(void))reply;
|
||||
- (void)setXsrfToken:(NSString *)token reply:(void (^)(void))reply;
|
||||
- (void)setFullSyncLastSuccess:(NSDate *)date reply:(void (^)(void))reply;
|
||||
- (void)setRuleSyncLastSuccess:(NSDate *)date reply:(void (^)(void))reply;
|
||||
- (void)setSyncCleanRequired:(BOOL)cleanReqd reply:(void (^)(void))reply;
|
||||
- (void)setWhitelistPathRegex:(NSString *)pattern reply:(void (^)(void))reply;
|
||||
- (void)setBlacklistPathRegex:(NSString *)pattern reply:(void (^)(void))reply;
|
||||
- (void)setEnableBundles:(BOOL)bundlesEnabled reply:(void (^)(void))reply;
|
||||
- (void)setEnableTransitiveWhitelisting:(BOOL)enabled reply:(void (^)(void))reply;
|
||||
|
||||
///
|
||||
/// Syncd Ops
|
||||
///
|
||||
- (void)setSyncdListener:(NSXPCListenerEndpoint *)listener;
|
||||
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message reply:(void (^)(void))reply;
|
||||
|
||||
@end
|
||||
|
||||
@@ -53,12 +62,23 @@
|
||||
///
|
||||
/// Returns the MachService ID for this service.
|
||||
///
|
||||
+ (NSString *)serviceId;
|
||||
+ (NSString *)serviceID;
|
||||
|
||||
///
|
||||
/// Returns an initialized NSXPCInterface for the SNTDaemonControlXPC protocol.
|
||||
/// Returns the SystemExtension ID for this service.
|
||||
///
|
||||
+ (NSString *)systemExtensionID;
|
||||
|
||||
///
|
||||
/// Returns an initialized NSXPCInterface for the SNTUnprivilegedDaemonControlXPC protocol.
|
||||
/// Ensures any methods that accept custom classes as arguments are set-up before returning
|
||||
///
|
||||
+ (NSXPCInterface *)controlInterface;
|
||||
|
||||
///
|
||||
/// Retrieve a pre-configured MOLXPCConnection for communicating with santad.
|
||||
/// Connections just needs any handlers set and then can be resumed and used.
|
||||
///
|
||||
+ (MOLXPCConnection *)configuredConnection;
|
||||
|
||||
@end
|
||||
|
||||
@@ -12,20 +12,35 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTXPCControlInterface.h"
|
||||
#import "Source/common/SNTXPCControlInterface.h"
|
||||
|
||||
#import "SNTRule.h"
|
||||
#import "SNTStoredEvent.h"
|
||||
#import <MOLCodesignChecker/MOLCodesignChecker.h>
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTRule.h"
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
|
||||
NSString *const kBundleID = @"com.google.santa.daemon";
|
||||
|
||||
@implementation SNTXPCControlInterface
|
||||
|
||||
+ (NSString *)serviceId {
|
||||
return @"SantaXPCControl";
|
||||
+ (NSString *)serviceID {
|
||||
if ([[SNTConfigurator configurator] enableSystemExtension]) {
|
||||
MOLCodesignChecker *cs = [[MOLCodesignChecker alloc] initWithSelf];
|
||||
// "teamid.com.google.santa.daemon.xpc"
|
||||
NSString *t = cs.signingInformation[@"teamid"];
|
||||
return [NSString stringWithFormat:@"%@.%@.xpc", t, kBundleID];
|
||||
}
|
||||
return kBundleID;
|
||||
}
|
||||
|
||||
+ (NSXPCInterface *)controlInterface {
|
||||
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTDaemonControlXPC)];
|
||||
+ (NSString *)systemExtensionID {
|
||||
return kBundleID;
|
||||
}
|
||||
|
||||
+ (void)initializeControlInterface:(NSXPCInterface *)r {
|
||||
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
|
||||
forSelector:@selector(databaseEventsPending:)
|
||||
argumentIndex:0
|
||||
@@ -35,8 +50,20 @@
|
||||
forSelector:@selector(databaseRuleAddRules:cleanSlate:reply:)
|
||||
argumentIndex:0
|
||||
ofReply:NO];
|
||||
}
|
||||
|
||||
+ (NSXPCInterface *)controlInterface {
|
||||
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTDaemonControlXPC)];
|
||||
[self initializeControlInterface:r];
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
+ (MOLXPCConnection *)configuredConnection {
|
||||
MOLXPCConnection *c = [[MOLXPCConnection alloc] initClientWithName:[self serviceID]
|
||||
privileged:YES];
|
||||
c.remoteInterface = [self controlInterface];
|
||||
return c;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -12,19 +12,26 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
/// Protocol implemented by SantaNotifier and utilized by santad
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#import "Source/common/SNTXPCBundleServiceInterface.h"
|
||||
|
||||
@class SNTStoredEvent;
|
||||
|
||||
/// Protocol implemented by SantaGUI and utilized by santad
|
||||
@protocol SNTNotifierXPC
|
||||
- (void)postBlockNotification:(SNTStoredEvent *)event withCustomMessage:(NSString *)message;
|
||||
- (void)postClientModeNotification:(SNTClientMode)clientmode;
|
||||
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message;
|
||||
- (void)updateCountsForEvent:(SNTStoredEvent *)event
|
||||
binaryCount:(uint64_t)binaryCount
|
||||
fileCount:(uint64_t)fileCount
|
||||
hashedCount:(uint64_t)hashedCount;
|
||||
@end
|
||||
|
||||
@interface SNTXPCNotifierInterface : NSObject
|
||||
|
||||
///
|
||||
/// @return the MachService ID for this service.
|
||||
///
|
||||
+ (NSString *)serviceId;
|
||||
|
||||
///
|
||||
/// @return an initialized NSXPCInterface for the SNTNotifierXPC protocol.
|
||||
/// Ensures any methods that accept custom classes as arguments are set-up before returning
|
||||
|
||||
@@ -12,17 +12,12 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTXPCNotifierInterface.h"
|
||||
#import "Source/common/SNTXPCNotifierInterface.h"
|
||||
|
||||
@implementation SNTXPCNotifierInterface
|
||||
|
||||
+ (NSString *)serviceId {
|
||||
return @"SantaXPCNotifications";
|
||||
}
|
||||
|
||||
+ (NSXPCInterface *)notifierInterface {
|
||||
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTNotifierXPC)];
|
||||
return r;
|
||||
return [NSXPCInterface interfaceWithProtocol:@protocol(SNTNotifierXPC)];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
37
Source/common/SNTXPCSyncdInterface.h
Normal file
37
Source/common/SNTXPCSyncdInterface.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/// Copyright 2016 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
|
||||
@class SNTStoredEvent;
|
||||
|
||||
/// Protocol implemented by santactl and utilized by santad
|
||||
@protocol SNTSyncdXPC
|
||||
- (void)postEventsToSyncServer:(NSArray<SNTStoredEvent *> *)events isFromBundle:(BOOL)isFromBundle;
|
||||
- (void)postBundleEventToSyncServer:(SNTStoredEvent *)event
|
||||
reply:(void (^)(SNTBundleEventAction))reply;
|
||||
- (void)isFCMListening:(void (^)(BOOL))reply;
|
||||
@end
|
||||
|
||||
@interface SNTXPCSyncdInterface : NSObject
|
||||
|
||||
///
|
||||
/// Returns an initialized NSXPCInterface for the SNTSyncdXPC protocol.
|
||||
/// Ensures any methods that accept custom classes as arguments are set-up before returning
|
||||
///
|
||||
+ (NSXPCInterface *)syncdInterface;
|
||||
|
||||
@end
|
||||
@@ -1,4 +1,4 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
/// Copyright 2016 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
@@ -12,14 +12,21 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
@class SNTCommandSyncState;
|
||||
@class SNTXPCConnection;
|
||||
#import "Source/common/SNTXPCSyncdInterface.h"
|
||||
|
||||
@interface SNTCommandSyncRuleDownload : NSObject
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
|
||||
+ (void)performSyncInSession:(NSURLSession *)session
|
||||
syncState:(SNTCommandSyncState *)syncState
|
||||
daemonConn:(SNTXPCConnection *)daemonConn
|
||||
completionHandler:(void (^)(BOOL success))handler;
|
||||
@implementation SNTXPCSyncdInterface
|
||||
|
||||
+ (NSXPCInterface *)syncdInterface {
|
||||
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTSyncdXPC)];
|
||||
|
||||
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
|
||||
forSelector:@selector(postEventsToSyncServer:isFromBundle:)
|
||||
argumentIndex:0
|
||||
ofReply:NO];
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
@end
|
||||
106
Source/common/SNTXPCUnprivilegedControlInterface.h
Normal file
106
Source/common/SNTXPCUnprivilegedControlInterface.h
Normal file
@@ -0,0 +1,106 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#import "Source/common/SNTKernelCommon.h"
|
||||
|
||||
@class SNTRule;
|
||||
@class SNTStoredEvent;
|
||||
@class MOLXPCConnection;
|
||||
|
||||
///
|
||||
/// Protocol implemented by santad and utilized by santactl (unprivileged operations)
|
||||
///
|
||||
@protocol SNTUnprivilegedDaemonControlXPC
|
||||
|
||||
///
|
||||
/// Kernel ops
|
||||
///
|
||||
- (void)cacheCounts:(void (^)(uint64_t rootCache, uint64_t nonRootCache))reply;
|
||||
- (void)cacheBucketCount:(void (^)(NSArray *))reply;
|
||||
- (void)checkCacheForVnodeID:(santa_vnode_id_t)vnodeID withReply:(void (^)(santa_action_t))reply;
|
||||
- (void)driverConnectionEstablished:(void (^)(BOOL))reply;
|
||||
|
||||
///
|
||||
/// Database ops
|
||||
///
|
||||
- (void)databaseRuleCounts:(void (^)(int64_t binary,
|
||||
int64_t certificate,
|
||||
int64_t compiler,
|
||||
int64_t transitive))reply;
|
||||
- (void)databaseEventCount:(void (^)(int64_t count))reply;
|
||||
|
||||
///
|
||||
/// Decision ops
|
||||
///
|
||||
|
||||
///
|
||||
/// @param filePath A Path to the file, can be nil.
|
||||
/// @param fileSHA256 The pre-calculated SHA256 hash for the file, can be nil. If nil the hash will
|
||||
/// be calculated by this method from the filePath.
|
||||
/// @param certificateSHA256 A SHA256 hash of the signing certificate, can be nil.
|
||||
/// @note If fileInfo and signingCertificate are both passed in, the most specific rule will be
|
||||
/// returned. Binary rules take precedence over cert rules.
|
||||
///
|
||||
- (void)decisionForFilePath:(NSString *)filePath
|
||||
fileSHA256:(NSString *)fileSHA256
|
||||
certificateSHA256:(NSString *)certificateSHA256
|
||||
reply:(void (^)(SNTEventState))reply;
|
||||
|
||||
///
|
||||
/// Config ops
|
||||
///
|
||||
- (void)watchdogInfo:(void (^)(uint64_t, uint64_t, double, double))reply;
|
||||
- (void)xsrfToken:(void (^)(NSString *))reply;
|
||||
- (void)clientMode:(void (^)(SNTClientMode))reply;
|
||||
- (void)fullSyncLastSuccess:(void (^)(NSDate *))reply;
|
||||
- (void)ruleSyncLastSuccess:(void (^)(NSDate *))reply;
|
||||
- (void)syncCleanRequired:(void (^)(BOOL))reply;
|
||||
- (void)enableBundles:(void (^)(BOOL))reply;
|
||||
- (void)enableTransitiveWhitelisting:(void (^)(BOOL))reply;
|
||||
|
||||
///
|
||||
/// GUI Ops
|
||||
///
|
||||
- (void)setNotificationListener:(NSXPCListenerEndpoint *)listener;
|
||||
|
||||
///
|
||||
/// Syncd Ops
|
||||
///
|
||||
- (void)pushNotifications:(void (^)(BOOL))reply;
|
||||
|
||||
///
|
||||
/// Bundle Ops
|
||||
///
|
||||
- (void)syncBundleEvent:(SNTStoredEvent *)event relatedEvents:(NSArray<SNTStoredEvent *> *)events;
|
||||
|
||||
@end
|
||||
|
||||
@interface SNTXPCUnprivilegedControlInterface : NSObject
|
||||
|
||||
///
|
||||
/// Returns an initialized NSXPCInterface for the SNTUnprivilegedDaemonControlXPC protocol.
|
||||
/// Ensures any methods that accept custom classes as arguments are set-up before returning
|
||||
///
|
||||
+ (NSXPCInterface *)controlInterface;
|
||||
|
||||
///
|
||||
/// Internal method used to initialize the control interface
|
||||
///
|
||||
+ (void)initializeControlInterface:(NSXPCInterface *)r;
|
||||
|
||||
@end
|
||||
38
Source/common/SNTXPCUnprivilegedControlInterface.m
Normal file
38
Source/common/SNTXPCUnprivilegedControlInterface.m
Normal file
@@ -0,0 +1,38 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/common/SNTXPCUnprivilegedControlInterface.h"
|
||||
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
#import "Source/common/SNTRule.h"
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
|
||||
@implementation SNTXPCUnprivilegedControlInterface
|
||||
|
||||
+ (void)initializeControlInterface:(NSXPCInterface *)r {
|
||||
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
|
||||
forSelector:@selector(syncBundleEvent:relatedEvents:)
|
||||
argumentIndex:1
|
||||
ofReply:NO];
|
||||
}
|
||||
|
||||
+ (NSXPCInterface *)controlInterface {
|
||||
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTUnprivilegedDaemonControlXPC)];
|
||||
[self initializeControlInterface:r];
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
@end
|
||||
52
Source/common/testdata/BundleExample.app/Contents/Info.plist
vendored
Normal file
52
Source/common/testdata/BundleExample.app/Contents/Info.plist
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BuildMachineOSBuild</key>
|
||||
<string>16F73</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>BundleExample</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.google.santa.BundleExample</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>BundleExample</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>DTCompiler</key>
|
||||
<string>com.apple.compilers.llvm.clang.1_0</string>
|
||||
<key>DTPlatformBuild</key>
|
||||
<string>7D1014</string>
|
||||
<key>DTPlatformVersion</key>
|
||||
<string>GM</string>
|
||||
<key>DTSDKBuild</key>
|
||||
<string>15E60</string>
|
||||
<key>DTSDKName</key>
|
||||
<string>macosx10.11</string>
|
||||
<key>DTXcode</key>
|
||||
<string>0731</string>
|
||||
<key>DTXcodeBuild</key>
|
||||
<string>7D1014</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.12</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2017 Google. All rights reserved.</string>
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
</dict>
|
||||
</plist>
|
||||
BIN
Source/common/testdata/BundleExample.app/Contents/MacOS/BundleExample
vendored
Executable file
BIN
Source/common/testdata/BundleExample.app/Contents/MacOS/BundleExample
vendored
Executable file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user