@expo/build-tools 24.2.0 → 24.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/common/projectSources.js +3 -0
  2. package/dist/steps/easFunctions.js +2 -0
  3. package/dist/steps/functions/startIosSimulator.js +34 -2
  4. package/dist/steps/functions/startLocalEgress.d.ts +4 -3
  5. package/dist/steps/functions/startLocalEgress.js +10 -6
  6. package/dist/steps/functions/startSandbox.d.ts +4 -0
  7. package/dist/steps/functions/startSandbox.js +93 -0
  8. package/dist/steps/utils/localEgress.d.ts +24 -0
  9. package/dist/steps/utils/localEgress.js +59 -1
  10. package/dist/steps/utils/localEgressGuard.d.ts +179 -0
  11. package/dist/steps/utils/localEgressGuard.js +537 -0
  12. package/dist/steps/utils/localEgressSession.js +4 -0
  13. package/dist/steps/utils/sandboxDaemon.d.ts +13 -0
  14. package/dist/steps/utils/sandboxDaemon.js +90 -0
  15. package/dist/utils/IosSimulatorUtils.d.ts +34 -0
  16. package/dist/utils/IosSimulatorUtils.js +62 -0
  17. package/package.json +8 -4
  18. package/resources/egress-guard/README.md +83 -0
  19. package/resources/egress-guard/build.sh +30 -0
  20. package/resources/egress-guard/check.c +112 -0
  21. package/resources/egress-guard/guard.c +274 -0
  22. package/resources/egress-guard/policy.c +164 -0
  23. package/resources/egress-guard/policy.h +60 -0
  24. package/resources/egress-guard/tests/guard_insert_test.c +291 -0
  25. package/resources/egress-guard/tests/guard_test.c +177 -0
  26. package/resources/egress-guard/tests/nettest.swift +172 -0
  27. package/resources/egress-guard/tests/policy_test.c +143 -0
  28. package/resources/egress-guard/tests/run-guard-tests.sh +62 -0
  29. package/resources/egress-guard/tests/run-policy-tests.sh +7 -0
@@ -0,0 +1,172 @@
1
+ // Probe run inside a simulator by the egress guard end-to-end test. Exercises
2
+ // every outbound path the guard must cover and prints one JSON object.
3
+ //
4
+ // nettest --proxy-port <port> --udp-port <port>
5
+ //
6
+ // proxy-port: a CONNECT proxy on the host's loopback, standing in for the
7
+ // egress proxy. udp-port: a UDP echo on loopback.
8
+ import Foundation
9
+ import Network
10
+
11
+ var results: [String: String] = [:]
12
+ let args = CommandLine.arguments
13
+ func arg(_ name: String) -> Int {
14
+ guard let i = args.firstIndex(of: name), i + 1 < args.count else { return 0 }
15
+ return Int(args[i + 1]) ?? 0
16
+ }
17
+ let proxyPort = arg("--proxy-port")
18
+ let udpPort = arg("--udp-port")
19
+ let remoteHost = "example.com"
20
+ let remotePort = 443
21
+
22
+ func classify(_ error: Error?) -> String {
23
+ guard let e = error as NSError? else { return "ok" }
24
+ if e.domain == NSURLErrorDomain && e.code == NSURLErrorCannotConnectToHost { return "refused" }
25
+ if e.domain == NSPOSIXErrorDomain && e.code == Int(ECONNREFUSED) { return "refused" }
26
+ return "error(\(e.domain):\(e.code))"
27
+ }
28
+
29
+ // 1. URLSession straight to the internet. Blocked by the guard.
30
+ func urlSessionDirect() {
31
+ let g = DispatchGroup(); g.enter()
32
+ let c = URLSessionConfiguration.ephemeral; c.timeoutIntervalForRequest = 10
33
+ c.connectionProxyDictionary = [:] // explicitly no proxy, even if the host has one
34
+ URLSession(configuration: c).dataTask(with: URL(string: "https://\(remoteHost)/")!) { _, r, e in
35
+ results["urlsessionDirect"] = e == nil ? "ok(\((r as? HTTPURLResponse)?.statusCode ?? 0))" : classify(e); g.leave()
36
+ }.resume(); g.wait()
37
+ }
38
+
39
+ // 2. URLSession through a CONNECT proxy on loopback. Allowed: the guard only
40
+ // sees a loopback connect.
41
+ func urlSessionProxied() {
42
+ let g = DispatchGroup(); g.enter()
43
+ let c = URLSessionConfiguration.ephemeral; c.timeoutIntervalForRequest = 15
44
+ // The kCFNetworkProxies* constants are not exposed to Swift on iOS; these are their values.
45
+ c.connectionProxyDictionary = ["HTTPSEnable": 1, "HTTPSProxy": "127.0.0.1", "HTTPSPort": proxyPort]
46
+ URLSession(configuration: c).dataTask(with: URL(string: "https://\(remoteHost)/")!) { _, r, e in
47
+ results["urlsessionProxied"] = e == nil ? "ok(\((r as? HTTPURLResponse)?.statusCode ?? 0))" : classify(e); g.leave()
48
+ }.resume(); g.wait()
49
+ }
50
+
51
+ // 3. Network.framework connection straight out. Blocked.
52
+ func nwConnectionDirect() {
53
+ let g = DispatchGroup(); g.enter()
54
+ let p = NWParameters.tls; p.preferNoProxies = true
55
+ let c = NWConnection(host: NWEndpoint.Host(remoteHost), port: NWEndpoint.Port(integerLiteral: UInt16(remotePort)), using: p)
56
+ var done = false
57
+ c.stateUpdateHandler = { s in
58
+ guard !done else { return }
59
+ switch s {
60
+ case .ready: done = true; results["nwconnectionDirect"] = "ok"; c.cancel(); g.leave()
61
+ case .failed(let e): done = true; results["nwconnectionDirect"] = classify(e); g.leave()
62
+ case .waiting(let e): done = true; results["nwconnectionDirect"] = "waiting(" + classify(e) + ")"; c.cancel(); g.leave()
63
+ default: break
64
+ }
65
+ }
66
+ c.start(queue: .global())
67
+ _ = g.wait(timeout: .now() + 15)
68
+ if results["nwconnectionDirect"] == nil { results["nwconnectionDirect"] = "timeout"; c.cancel() }
69
+ }
70
+
71
+ // 4. Plain BSD TCP connect to a resolved address. Blocked.
72
+ func bsdConnectDirect() {
73
+ var hints = addrinfo(); hints.ai_socktype = SOCK_STREAM; hints.ai_family = AF_INET
74
+ var info: UnsafeMutablePointer<addrinfo>? = nil
75
+ guard getaddrinfo(remoteHost, "443", &hints, &info) == 0, let ai = info else { results["bsdConnectDirect"] = "dns-failed"; return }
76
+ results["dns"] = "ok"
77
+ let fd = socket(ai.pointee.ai_family, ai.pointee.ai_socktype, ai.pointee.ai_protocol)
78
+ let rc = connect(fd, ai.pointee.ai_addr, ai.pointee.ai_addrlen)
79
+ results["bsdConnectDirect"] = rc == 0 ? "ok" : (errno == ECONNREFUSED ? "refused" : "errno(\(errno))")
80
+ close(fd); freeaddrinfo(info)
81
+ }
82
+
83
+ // 5. UDP datagram straight out. Blocked. 6. UDP datagram to loopback echo. Allowed.
84
+ func udp(_ key: String, _ host: String, _ port: Int, expectEcho: Bool) {
85
+ let fd = socket(AF_INET, SOCK_DGRAM, 0)
86
+ var to = sockaddr_in(); to.sin_family = sa_family_t(AF_INET); to.sin_port = in_port_t(UInt16(port)).bigEndian
87
+ inet_pton(AF_INET, host, &to.sin_addr)
88
+ var payload: [UInt8] = Array("ping".utf8)
89
+ let sent = withUnsafePointer(to: &to) { p in p.withMemoryRebound(to: sockaddr.self, capacity: 1) { sa in
90
+ sendto(fd, &payload, payload.count, 0, sa, socklen_t(MemoryLayout<sockaddr_in>.size)) } }
91
+ if sent < 0 { results[key] = errno == ECONNREFUSED ? "refused" : "errno(\(errno))"; close(fd); return }
92
+ if expectEcho {
93
+ var tv = timeval(tv_sec: 3, tv_usec: 0); setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, socklen_t(MemoryLayout<timeval>.size))
94
+ var buf = [UInt8](repeating: 0, count: 16)
95
+ let n = recv(fd, &buf, buf.count, 0)
96
+ results[key] = n > 0 ? "ok" : "no-echo"
97
+ } else { results[key] = "sent" }
98
+ close(fd)
99
+ }
100
+
101
+ urlSessionDirect()
102
+ urlSessionProxied()
103
+ nwConnectionDirect()
104
+ bsdConnectDirect()
105
+ udp("udpDirect", "8.8.8.8", 53, expectEcho: false)
106
+ udp("udpLoopback", "127.0.0.1", udpPort, expectEcho: true)
107
+
108
+ // 7. The same literal destination twice: the guard must log it once per process.
109
+ func bsdConnectLiteral(_ key: String) {
110
+ var to = sockaddr_in(); to.sin_family = sa_family_t(AF_INET); to.sin_port = in_port_t(UInt16(443)).bigEndian
111
+ inet_pton(AF_INET, "1.1.1.1", &to.sin_addr)
112
+ let fd = socket(AF_INET, SOCK_STREAM, 0)
113
+ let rc = withUnsafePointer(to: &to) { p in p.withMemoryRebound(to: sockaddr.self, capacity: 1) { sa in
114
+ connect(fd, sa, socklen_t(MemoryLayout<sockaddr_in>.size)) } }
115
+ results[key] = rc == 0 ? "ok" : (errno == ECONNREFUSED ? "refused" : "errno(\(errno))")
116
+ close(fd)
117
+ }
118
+ bsdConnectLiteral("literalFirst")
119
+ bsdConnectLiteral("literalSecond")
120
+
121
+ // 8. A sockaddr_in whose family was left AF_UNSPEC: the kernel connects it as
122
+ // IPv4, so the guard must refuse it. 9. connect(AF_UNSPEC) on a UDP socket
123
+ // dissolves the association; the guard must leave that to the kernel.
124
+ func unspecConnect() {
125
+ var to = sockaddr_in(); to.sin_family = sa_family_t(AF_UNSPEC); to.sin_port = in_port_t(UInt16(443)).bigEndian
126
+ inet_pton(AF_INET, "1.0.0.1", &to.sin_addr)
127
+ let fd = socket(AF_INET, SOCK_STREAM, 0)
128
+ let rc = withUnsafePointer(to: &to) { p in p.withMemoryRebound(to: sockaddr.self, capacity: 1) { sa in
129
+ connect(fd, sa, socklen_t(MemoryLayout<sockaddr_in>.size)) } }
130
+ results["unspecConnect"] = rc == 0 ? "ok" : (errno == ECONNREFUSED ? "refused" : "errno(\(errno))")
131
+ close(fd)
132
+ }
133
+ func udpDisconnect() {
134
+ let fd = socket(AF_INET, SOCK_DGRAM, 0)
135
+ var to = sockaddr_in(); to.sin_family = sa_family_t(AF_INET); to.sin_port = in_port_t(UInt16(udpPort)).bigEndian
136
+ inet_pton(AF_INET, "127.0.0.1", &to.sin_addr)
137
+ let associated = withUnsafePointer(to: &to) { p in p.withMemoryRebound(to: sockaddr.self, capacity: 1) { sa in
138
+ connect(fd, sa, socklen_t(MemoryLayout<sockaddr_in>.size)) } }
139
+ var dissolve = sockaddr_in(); dissolve.sin_family = sa_family_t(AF_UNSPEC); dissolve.sin_port = in_port_t(UInt16(443)).bigEndian
140
+ inet_pton(AF_INET, "1.1.1.1", &dissolve.sin_addr)
141
+ let rc = withUnsafePointer(to: &dissolve) { p in p.withMemoryRebound(to: sockaddr.self, capacity: 1) { sa in
142
+ connect(fd, sa, socklen_t(MemoryLayout<sockaddr_in>.size)) } }
143
+ let err = errno
144
+ var peer = sockaddr_in(); var peerLength = socklen_t(MemoryLayout<sockaddr_in>.size)
145
+ let stillConnected = withUnsafeMutablePointer(to: &peer) { p in p.withMemoryRebound(to: sockaddr.self, capacity: 1) { sa in
146
+ getpeername(fd, sa, &peerLength) } } == 0
147
+ results["udpDisconnect"] = associated != 0 ? "associate-failed"
148
+ : (rc == -1 && err == EAFNOSUPPORT && !stillConnected) ? "ok" : "errno(\(err)),connected=\(stillConnected)"
149
+ close(fd)
150
+ }
151
+ // 10. The non-cancelable variant of sendto, as libsystem exports it. Blocked.
152
+ func nocancelSendto() {
153
+ typealias SendtoFn = @convention(c) (Int32, UnsafeRawPointer?, Int, Int32, UnsafePointer<sockaddr>?, socklen_t) -> Int
154
+ guard let symbol = dlsym(UnsafeMutableRawPointer(bitPattern: -2), "sendto$NOCANCEL") else {
155
+ results["nocancelSendto"] = "no-symbol"; return
156
+ }
157
+ let nocancelSendto = unsafeBitCast(symbol, to: SendtoFn.self)
158
+ let fd = socket(AF_INET, SOCK_DGRAM, 0)
159
+ var to = sockaddr_in(); to.sin_family = sa_family_t(AF_INET); to.sin_port = in_port_t(UInt16(53)).bigEndian
160
+ inet_pton(AF_INET, "8.8.8.8", &to.sin_addr)
161
+ var payload: [UInt8] = Array("ping".utf8)
162
+ let sent = withUnsafePointer(to: &to) { p in p.withMemoryRebound(to: sockaddr.self, capacity: 1) { sa in
163
+ nocancelSendto(fd, &payload, payload.count, 0, sa, socklen_t(MemoryLayout<sockaddr_in>.size)) } }
164
+ results["nocancelSendto"] = sent < 0 ? (errno == ECONNREFUSED ? "refused" : "errno(\(errno))") : "sent"
165
+ close(fd)
166
+ }
167
+ unspecConnect()
168
+ udpDisconnect()
169
+ nocancelSendto()
170
+
171
+ let json = try! JSONSerialization.data(withJSONObject: results, options: [.sortedKeys])
172
+ print(String(data: json, encoding: .utf8)!)
@@ -0,0 +1,143 @@
1
+ // Host-runnable tests for the guard's policy and formatting. Build and run
2
+ // with tests/run-policy-tests.sh; every failure prints the failing line.
3
+ #include "../policy.h"
4
+
5
+ #include <arpa/inet.h>
6
+ #include <netinet/in.h>
7
+ #include <stdio.h>
8
+ #include <string.h>
9
+ #include <sys/un.h>
10
+
11
+ static int failures = 0;
12
+ #define CHECK(cond) \
13
+ do { \
14
+ if (!(cond)) { \
15
+ failures++; \
16
+ fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \
17
+ } \
18
+ } while (0)
19
+
20
+ static struct sockaddr_in v4(const char *ip, int port) {
21
+ struct sockaddr_in a; memset(&a, 0, sizeof a);
22
+ a.sin_family = AF_INET; a.sin_len = sizeof a; a.sin_port = htons(port);
23
+ inet_pton(AF_INET, ip, &a.sin_addr);
24
+ return a;
25
+ }
26
+ static struct sockaddr_in6 v6(const char *ip, int port) {
27
+ struct sockaddr_in6 a; memset(&a, 0, sizeof a);
28
+ a.sin6_family = AF_INET6; a.sin6_len = sizeof a; a.sin6_port = htons(port);
29
+ inet_pton(AF_INET6, ip, &a.sin6_addr);
30
+ return a;
31
+ }
32
+
33
+ static void test_classify(void) {
34
+ struct sockaddr_in a4; struct sockaddr_in6 a6; struct sockaddr_un un;
35
+
36
+ a4 = v4("127.0.0.1", 8899); CHECK(eg_classify((struct sockaddr *)&a4, sizeof a4) == EG_LOOPBACK);
37
+ a4 = v4("127.42.0.9", 80); CHECK(eg_classify((struct sockaddr *)&a4, sizeof a4) == EG_LOOPBACK);
38
+ a4 = v4("0.0.0.0", 80); CHECK(eg_classify((struct sockaddr *)&a4, sizeof a4) == EG_LOOPBACK);
39
+ a4 = v4("93.184.216.34", 443); CHECK(eg_classify((struct sockaddr *)&a4, sizeof a4) == EG_REMOTE);
40
+ a4 = v4("192.168.1.10", 8081); CHECK(eg_classify((struct sockaddr *)&a4, sizeof a4) == EG_REMOTE);
41
+ a4 = v4("169.254.1.1", 80); CHECK(eg_classify((struct sockaddr *)&a4, sizeof a4) == EG_REMOTE);
42
+ a4 = v4("224.0.0.251", 5353); CHECK(eg_classify((struct sockaddr *)&a4, sizeof a4) == EG_REMOTE);
43
+
44
+ a6 = v6("::1", 8899); CHECK(eg_classify((struct sockaddr *)&a6, sizeof a6) == EG_LOOPBACK);
45
+ a6 = v6("::", 80); CHECK(eg_classify((struct sockaddr *)&a6, sizeof a6) == EG_LOOPBACK);
46
+ a6 = v6("::ffff:127.0.0.1", 8899); CHECK(eg_classify((struct sockaddr *)&a6, sizeof a6) == EG_LOOPBACK);
47
+ a6 = v6("::ffff:93.184.216.34", 443); CHECK(eg_classify((struct sockaddr *)&a6, sizeof a6) == EG_REMOTE);
48
+ a6 = v6("2606:4700::1", 443); CHECK(eg_classify((struct sockaddr *)&a6, sizeof a6) == EG_REMOTE);
49
+ a6 = v6("fe80::1", 443); CHECK(eg_classify((struct sockaddr *)&a6, sizeof a6) == EG_REMOTE);
50
+
51
+ memset(&un, 0, sizeof un); un.sun_family = AF_UNIX; strcpy(un.sun_path, "/var/run/mDNSResponder");
52
+ CHECK(eg_classify((struct sockaddr *)&un, sizeof un) == EG_PASSTHROUGH);
53
+ CHECK(eg_classify(NULL, 0) == EG_PASSTHROUGH);
54
+ a4 = v4("93.184.216.34", 443); CHECK(eg_classify((struct sockaddr *)&a4, 4) == EG_PASSTHROUGH); // too short to read
55
+
56
+ // AF_UNSPEC is what the kernel makes of it: the family the exact length implies.
57
+ a4 = v4("93.184.216.34", 443); a4.sin_family = AF_UNSPEC;
58
+ CHECK(eg_classify((struct sockaddr *)&a4, sizeof a4) == EG_REMOTE);
59
+ CHECK(eg_classify((struct sockaddr *)&a4, sizeof a4 + 8) == EG_PASSTHROUGH); // kernel: EINVAL
60
+ a4 = v4("127.0.0.1", 8899); a4.sin_family = AF_UNSPEC;
61
+ CHECK(eg_classify((struct sockaddr *)&a4, sizeof a4) == EG_LOOPBACK);
62
+ a4 = v4("0.0.0.0", 0); a4.sin_family = AF_UNSPEC;
63
+ CHECK(eg_classify((struct sockaddr *)&a4, sizeof a4) == EG_LOOPBACK); // UDP disconnect shape
64
+ a6 = v6("2606:4700::1", 443); a6.sin6_family = AF_UNSPEC;
65
+ CHECK(eg_classify((struct sockaddr *)&a6, sizeof a6) == EG_REMOTE);
66
+ a6 = v6("::ffff:93.184.216.34", 443); a6.sin6_family = AF_UNSPEC;
67
+ CHECK(eg_classify((struct sockaddr *)&a6, sizeof a6) == EG_REMOTE);
68
+ a6 = v6("::1", 443); a6.sin6_family = AF_UNSPEC;
69
+ CHECK(eg_classify((struct sockaddr *)&a6, sizeof a6) == EG_LOOPBACK);
70
+ memset(&un, 0, sizeof un); un.sun_family = AF_UNSPEC;
71
+ CHECK(eg_classify((struct sockaddr *)&un, sizeof un) == EG_PASSTHROUGH);
72
+ }
73
+
74
+ static void test_mode(void) {
75
+ CHECK(eg_parse_mode(NULL) == EG_MODE_BLOCK);
76
+ CHECK(eg_parse_mode("") == EG_MODE_BLOCK);
77
+ CHECK(eg_parse_mode("block") == EG_MODE_BLOCK);
78
+ CHECK(eg_parse_mode("log") == EG_MODE_LOG);
79
+ CHECK(eg_parse_mode("LOG") == EG_MODE_BLOCK);
80
+ CHECK(eg_parse_mode("permissive") == EG_MODE_BLOCK);
81
+
82
+ CHECK(eg_should_deny(EG_MODE_BLOCK, EG_REMOTE) == 1);
83
+ CHECK(eg_should_deny(EG_MODE_BLOCK, EG_LOOPBACK) == 0);
84
+ CHECK(eg_should_deny(EG_MODE_BLOCK, EG_PASSTHROUGH) == 0);
85
+ CHECK(eg_should_deny(EG_MODE_LOG, EG_REMOTE) == 0);
86
+ }
87
+
88
+ static void test_format_peer(void) {
89
+ char out[64];
90
+ struct sockaddr_in a4 = v4("93.184.216.34", 443);
91
+ CHECK(eg_format_peer((struct sockaddr *)&a4, sizeof a4, out, sizeof out) == 0);
92
+ CHECK(strcmp(out, "93.184.216.34:443") == 0);
93
+ struct sockaddr_in6 a6 = v6("2606:4700::1", 443);
94
+ CHECK(eg_format_peer((struct sockaddr *)&a6, sizeof a6, out, sizeof out) == 0);
95
+ CHECK(strcmp(out, "[2606:4700::1]:443") == 0);
96
+ CHECK(eg_format_peer((struct sockaddr *)&a4, sizeof a4, out, 8) != 0);
97
+ struct sockaddr_un un; memset(&un, 0, sizeof un); un.sun_family = AF_UNIX;
98
+ CHECK(eg_format_peer((struct sockaddr *)&un, sizeof un, out, sizeof out) != 0);
99
+ a4.sin_family = AF_UNSPEC;
100
+ CHECK(eg_format_peer((struct sockaddr *)&a4, sizeof a4, out, sizeof out) == 0);
101
+ CHECK(strcmp(out, "93.184.216.34:443") == 0);
102
+ a6.sin6_family = AF_UNSPEC;
103
+ CHECK(eg_format_peer((struct sockaddr *)&a6, sizeof a6, out, sizeof out) == 0);
104
+ CHECK(strcmp(out, "[2606:4700::1]:443") == 0);
105
+ }
106
+
107
+ static void test_seen(void) {
108
+ static eg_seen_t seen; memset(&seen, 0, sizeof seen);
109
+ CHECK(eg_seen_insert(&seen, "connect 1.1.1.1:443") == 1);
110
+ CHECK(eg_seen_insert(&seen, "connect 1.1.1.1:443") == 0);
111
+ CHECK(eg_seen_insert(&seen, "sendto 1.1.1.1:443") == 1);
112
+ CHECK(seen.count == 2);
113
+ char key[64];
114
+ for (int i = 0; i < EG_SEEN_CAPACITY + 5; i++) { snprintf(key, sizeof key, "k%d", i); eg_seen_insert(&seen, key); }
115
+ CHECK(seen.count == EG_SEEN_CAPACITY);
116
+ CHECK(seen.overflow == 7); // 2 + 128 + 5 attempts, 128 fit
117
+ CHECK(eg_seen_insert(&seen, "k0") == 0); // still remembered
118
+ }
119
+
120
+ static void test_format_event(void) {
121
+ char out[512];
122
+ const char *callers[] = {"Network", "CFNetwork", "MyApp"};
123
+ int n = eg_format_event(out, sizeof out, "MyApp", 4242, "connect", "blocked", "93.184.216.34:443", callers, 3);
124
+ CHECK(n > 0);
125
+ CHECK(strcmp(out, "eas-egress-guard\tMyApp\t4242\tconnect\tblocked\t93.184.216.34:443\tNetwork,CFNetwork,MyApp\n") == 0);
126
+
127
+ const char *tabbed[] = {"a\tb"};
128
+ n = eg_format_event(out, sizeof out, "we\nird\tname", 1, "sendto", "logged", "1.2.3.4:53", tabbed, 1);
129
+ CHECK(n > 0);
130
+ CHECK(strcmp(out, "eas-egress-guard\twe ird name\t1\tsendto\tlogged\t1.2.3.4:53\ta b\n") == 0);
131
+
132
+ n = eg_format_event(out, sizeof out, "NoCallers", 7, "connectx", "blocked", "[::2]:80", NULL, 0);
133
+ CHECK(n > 0 && strcmp(out, "eas-egress-guard\tNoCallers\t7\tconnectx\tblocked\t[::2]:80\t\n") == 0);
134
+
135
+ CHECK(eg_format_event(out, 16, "MyApp", 4242, "connect", "blocked", "93.184.216.34:443", callers, 3) == -1);
136
+ }
137
+
138
+ int main(void) {
139
+ test_classify(); test_mode(); test_format_peer(); test_seen(); test_format_event();
140
+ if (failures) { fprintf(stderr, "%d failure(s)\n", failures); return 1; }
141
+ printf("egress-guard policy tests: all passed\n");
142
+ return 0;
143
+ }
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env bash
2
+ # Builds the guard for the host (macOS) and runs the guard tests against it:
3
+ # white-box lock schedules with guard.c compiled in, and black-box cases with
4
+ # the built library inserted the way the simulator inserts it. No socket is
5
+ # ever opened to a non-loopback address.
6
+ set -euo pipefail
7
+ cd "$(dirname "$0")/.."
8
+ out=$(mktemp -d)
9
+ trap 'rm -rf "$out"' EXIT
10
+
11
+ clang -std=c11 -Wall -Wextra -Werror -O2 -dynamiclib policy.c guard.c -o "$out/egress-guard.dylib"
12
+ clang -std=c11 -Wall -Wextra -Werror -O2 -pthread policy.c tests/guard_test.c -o "$out/guard-test"
13
+ clang -std=c11 -Wall -Wextra -Werror -O2 tests/guard_insert_test.c -o "$out/guard-insert-test"
14
+
15
+ failures=0
16
+ report() {
17
+ if [[ $2 -eq 0 ]]; then
18
+ echo "ok $1"
19
+ else
20
+ echo "FAIL $1 (exit $2)"
21
+ failures=$((failures + 1))
22
+ fi
23
+ }
24
+
25
+ # White-box: guard.c compiled into the test, lock schedules forced.
26
+ for name in signal fork contention; do
27
+ status=0
28
+ : > "$out/$name.log"
29
+ EAS_EGRESS_GUARD_MODE=block EAS_EGRESS_GUARD_LOG="$out/$name.log" \
30
+ "$out/guard-test" "$name" || status=$?
31
+ report "$name" "$status"
32
+ done
33
+
34
+ # Black-box: the built library inserted into a plain process.
35
+ export DYLD_INSERT_LIBRARIES="$out/egress-guard.dylib"
36
+ export EAS_EGRESS_GUARD_MODE=block
37
+
38
+ status=0
39
+ : > "$out/fd-reuse.log"
40
+ EAS_EGRESS_GUARD_LOG="$out/fd-reuse.log" \
41
+ "$out/guard-insert-test" fd-reuse "$out/fd-reuse.log" "$out/application-file" || status=$?
42
+ report fd-reuse "$status"
43
+
44
+ status=0
45
+ : > "$out/low-fd.log"
46
+ EAS_EGRESS_GUARD_LOG="$out/low-fd.log" \
47
+ "$out/guard-insert-test" low-fd "$out/low-fd.log" 1>&- || status=$?
48
+ report low-fd "$status"
49
+
50
+ for name in nocancel unspec udp-disconnect callers overflow; do
51
+ status=0
52
+ : > "$out/$name.log"
53
+ EAS_EGRESS_GUARD_LOG="$out/$name.log" \
54
+ "$out/guard-insert-test" "$name" "$out/$name.log" || status=$?
55
+ report "$name" "$status"
56
+ done
57
+
58
+ if [[ $failures -ne 0 ]]; then
59
+ echo "egress-guard guard tests: $failures failure(s)"
60
+ exit 1
61
+ fi
62
+ echo "egress-guard guard tests: all passed"
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env bash
2
+ # Builds the policy unit tests for the host (macOS) and runs them.
3
+ set -euo pipefail
4
+ cd "$(dirname "$0")/.."
5
+ out=$(mktemp -d)
6
+ clang -std=c11 -Wall -Wextra -Werror -o "$out/policy_test" policy.c tests/policy_test.c
7
+ "$out/policy_test"