@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.
- package/dist/common/projectSources.js +3 -0
- package/dist/steps/easFunctions.js +2 -0
- package/dist/steps/functions/startIosSimulator.js +34 -2
- package/dist/steps/functions/startLocalEgress.d.ts +4 -3
- package/dist/steps/functions/startLocalEgress.js +10 -6
- package/dist/steps/functions/startSandbox.d.ts +4 -0
- package/dist/steps/functions/startSandbox.js +93 -0
- package/dist/steps/utils/localEgress.d.ts +24 -0
- package/dist/steps/utils/localEgress.js +59 -1
- package/dist/steps/utils/localEgressGuard.d.ts +179 -0
- package/dist/steps/utils/localEgressGuard.js +537 -0
- package/dist/steps/utils/localEgressSession.js +4 -0
- package/dist/steps/utils/sandboxDaemon.d.ts +13 -0
- package/dist/steps/utils/sandboxDaemon.js +90 -0
- package/dist/utils/IosSimulatorUtils.d.ts +34 -0
- package/dist/utils/IosSimulatorUtils.js +62 -0
- package/package.json +8 -4
- package/resources/egress-guard/README.md +83 -0
- package/resources/egress-guard/build.sh +30 -0
- package/resources/egress-guard/check.c +112 -0
- package/resources/egress-guard/guard.c +274 -0
- package/resources/egress-guard/policy.c +164 -0
- package/resources/egress-guard/policy.h +60 -0
- package/resources/egress-guard/tests/guard_insert_test.c +291 -0
- package/resources/egress-guard/tests/guard_test.c +177 -0
- package/resources/egress-guard/tests/nettest.swift +172 -0
- package/resources/egress-guard/tests/policy_test.c +143 -0
- package/resources/egress-guard/tests/run-guard-tests.sh +62 -0
- package/resources/egress-guard/tests/run-policy-tests.sh +7 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#include "policy.h"
|
|
2
|
+
|
|
3
|
+
#include <arpa/inet.h>
|
|
4
|
+
#include <netinet/in.h>
|
|
5
|
+
#include <stdint.h>
|
|
6
|
+
#include <stdio.h>
|
|
7
|
+
#include <string.h>
|
|
8
|
+
|
|
9
|
+
static int is_v4_local(uint32_t host_order) {
|
|
10
|
+
return (host_order >> 24) == 127 || host_order == 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// XNU accepts a sockaddr whose family is AF_UNSPEC as the family its length
|
|
14
|
+
// implies: TCP connect() translates it on both socket families and IPv4
|
|
15
|
+
// sendto()/sendmsg() deliver with it. Classify it the way the kernel uses it.
|
|
16
|
+
static sa_family_t eg_family(const struct sockaddr *sa, socklen_t len) {
|
|
17
|
+
if (sa->sa_family == AF_UNSPEC) {
|
|
18
|
+
if (len == sizeof(struct sockaddr_in)) {
|
|
19
|
+
return AF_INET;
|
|
20
|
+
}
|
|
21
|
+
if (len == sizeof(struct sockaddr_in6)) {
|
|
22
|
+
return AF_INET6;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return sa->sa_family;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
eg_class_t eg_classify(const struct sockaddr *sa, socklen_t len) {
|
|
29
|
+
if (sa == NULL || len < 2) {
|
|
30
|
+
return EG_PASSTHROUGH;
|
|
31
|
+
}
|
|
32
|
+
sa_family_t family = eg_family(sa, len);
|
|
33
|
+
if (family == AF_INET) {
|
|
34
|
+
if (len < sizeof(struct sockaddr_in)) {
|
|
35
|
+
return EG_PASSTHROUGH;
|
|
36
|
+
}
|
|
37
|
+
const struct sockaddr_in *in = (const struct sockaddr_in *)sa;
|
|
38
|
+
return is_v4_local(ntohl(in->sin_addr.s_addr)) ? EG_LOOPBACK : EG_REMOTE;
|
|
39
|
+
}
|
|
40
|
+
if (family == AF_INET6) {
|
|
41
|
+
if (len < sizeof(struct sockaddr_in6)) {
|
|
42
|
+
return EG_PASSTHROUGH;
|
|
43
|
+
}
|
|
44
|
+
const struct in6_addr *a6 = &((const struct sockaddr_in6 *)sa)->sin6_addr;
|
|
45
|
+
if (IN6_IS_ADDR_LOOPBACK(a6) || IN6_IS_ADDR_UNSPECIFIED(a6)) {
|
|
46
|
+
return EG_LOOPBACK;
|
|
47
|
+
}
|
|
48
|
+
if (IN6_IS_ADDR_V4MAPPED(a6)) {
|
|
49
|
+
uint32_t v4;
|
|
50
|
+
memcpy(&v4, &a6->s6_addr[12], sizeof v4);
|
|
51
|
+
return is_v4_local(ntohl(v4)) ? EG_LOOPBACK : EG_REMOTE;
|
|
52
|
+
}
|
|
53
|
+
return EG_REMOTE;
|
|
54
|
+
}
|
|
55
|
+
return EG_PASSTHROUGH;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
eg_mode_t eg_parse_mode(const char *value) {
|
|
59
|
+
if (value != NULL && strcmp(value, "log") == 0) {
|
|
60
|
+
return EG_MODE_LOG;
|
|
61
|
+
}
|
|
62
|
+
return EG_MODE_BLOCK;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
int eg_should_deny(eg_mode_t mode, eg_class_t cls) {
|
|
66
|
+
return mode == EG_MODE_BLOCK && cls == EG_REMOTE;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
int eg_format_peer(const struct sockaddr *sa, socklen_t len, char *out, size_t n) {
|
|
70
|
+
char ip[INET6_ADDRSTRLEN];
|
|
71
|
+
int written;
|
|
72
|
+
if (sa == NULL || len < 2) {
|
|
73
|
+
return -1;
|
|
74
|
+
}
|
|
75
|
+
sa_family_t family = eg_family(sa, len);
|
|
76
|
+
if (family == AF_INET && len >= sizeof(struct sockaddr_in)) {
|
|
77
|
+
const struct sockaddr_in *in = (const struct sockaddr_in *)sa;
|
|
78
|
+
if (inet_ntop(AF_INET, &in->sin_addr, ip, sizeof ip) == NULL) {
|
|
79
|
+
return -1;
|
|
80
|
+
}
|
|
81
|
+
written = snprintf(out, n, "%s:%d", ip, ntohs(in->sin_port));
|
|
82
|
+
} else if (family == AF_INET6 && len >= sizeof(struct sockaddr_in6)) {
|
|
83
|
+
const struct sockaddr_in6 *in6 = (const struct sockaddr_in6 *)sa;
|
|
84
|
+
if (inet_ntop(AF_INET6, &in6->sin6_addr, ip, sizeof ip) == NULL) {
|
|
85
|
+
return -1;
|
|
86
|
+
}
|
|
87
|
+
written = snprintf(out, n, "[%s]:%d", ip, ntohs(in6->sin6_port));
|
|
88
|
+
} else {
|
|
89
|
+
return -1;
|
|
90
|
+
}
|
|
91
|
+
return (written < 0 || (size_t)written >= n) ? -1 : 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
int eg_seen_insert(eg_seen_t *seen, const char *key) {
|
|
95
|
+
for (int i = 0; i < seen->count; i++) {
|
|
96
|
+
if (strncmp(seen->keys[i], key, EG_SEEN_KEY_LENGTH - 1) == 0) {
|
|
97
|
+
return 0;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (seen->count >= EG_SEEN_CAPACITY) {
|
|
101
|
+
seen->overflow++;
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
strncpy(seen->keys[seen->count], key, EG_SEEN_KEY_LENGTH - 1);
|
|
105
|
+
seen->keys[seen->count][EG_SEEN_KEY_LENGTH - 1] = '\0';
|
|
106
|
+
seen->count++;
|
|
107
|
+
return 1;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Append `text` to out[*pos], replacing field separators; returns 0 when it fit.
|
|
111
|
+
static int append_field(char *out, size_t n, size_t *pos, const char *text) {
|
|
112
|
+
for (const char *c = text; *c; c++) {
|
|
113
|
+
if (*pos + 1 >= n) {
|
|
114
|
+
return -1;
|
|
115
|
+
}
|
|
116
|
+
out[(*pos)++] = (*c == '\t' || *c == '\n' || *c == '\r') ? ' ' : *c;
|
|
117
|
+
}
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
static int append_raw(char *out, size_t n, size_t *pos, const char *text) {
|
|
122
|
+
size_t len = strlen(text);
|
|
123
|
+
if (*pos + len + 1 > n) {
|
|
124
|
+
return -1;
|
|
125
|
+
}
|
|
126
|
+
memcpy(out + *pos, text, len);
|
|
127
|
+
*pos += len;
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
int eg_format_event(char *out, size_t n, const char *progname, int pid, const char *function,
|
|
132
|
+
const char *action, const char *peer, const char *const *callers,
|
|
133
|
+
int caller_count) {
|
|
134
|
+
size_t pos = 0;
|
|
135
|
+
char pid_text[16];
|
|
136
|
+
snprintf(pid_text, sizeof pid_text, "%d", pid);
|
|
137
|
+
if (append_raw(out, n, &pos, EG_EVENT_PREFIX "\t") != 0 ||
|
|
138
|
+
append_field(out, n, &pos, progname ? progname : "?") != 0 ||
|
|
139
|
+
append_raw(out, n, &pos, "\t") != 0 || append_raw(out, n, &pos, pid_text) != 0 ||
|
|
140
|
+
append_raw(out, n, &pos, "\t") != 0 || append_field(out, n, &pos, function) != 0 ||
|
|
141
|
+
append_raw(out, n, &pos, "\t") != 0 || append_field(out, n, &pos, action) != 0 ||
|
|
142
|
+
append_raw(out, n, &pos, "\t") != 0 || append_field(out, n, &pos, peer) != 0 ||
|
|
143
|
+
append_raw(out, n, &pos, "\t") != 0) {
|
|
144
|
+
return -1;
|
|
145
|
+
}
|
|
146
|
+
for (int i = 0; i < caller_count && callers != NULL; i++) {
|
|
147
|
+
if (i > 0 && append_raw(out, n, &pos, ",") != 0) {
|
|
148
|
+
return -1;
|
|
149
|
+
}
|
|
150
|
+
// Commas separate callers, so they cannot appear inside one.
|
|
151
|
+
for (const char *c = callers[i]; *c; c++) {
|
|
152
|
+
char ch = (*c == ',' || *c == '\t' || *c == '\n' || *c == '\r') ? ' ' : *c;
|
|
153
|
+
if (pos + 1 >= n) {
|
|
154
|
+
return -1;
|
|
155
|
+
}
|
|
156
|
+
out[pos++] = ch;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (append_raw(out, n, &pos, "\n") != 0) {
|
|
160
|
+
return -1;
|
|
161
|
+
}
|
|
162
|
+
out[pos] = '\0';
|
|
163
|
+
return (int)pos;
|
|
164
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Policy and formatting for the local egress guard. Pure functions with no
|
|
2
|
+
// interposing, so they compile and test on macOS as well as in the simulator.
|
|
3
|
+
#ifndef EAS_EGRESS_GUARD_POLICY_H
|
|
4
|
+
#define EAS_EGRESS_GUARD_POLICY_H
|
|
5
|
+
|
|
6
|
+
#include <stddef.h>
|
|
7
|
+
#include <sys/socket.h>
|
|
8
|
+
|
|
9
|
+
// What a destination address is, as far as the guard cares.
|
|
10
|
+
typedef enum {
|
|
11
|
+
EG_PASSTHROUGH = 0, // not an internet address (unix sockets, NULL, short); never touched
|
|
12
|
+
EG_LOOPBACK = 1, // 127.0.0.0/8, ::1, ::ffff:127.x, unspecified; the proxy and forwards live here
|
|
13
|
+
EG_REMOTE = 2, // anything else, including link-local and multicast
|
|
14
|
+
} eg_class_t;
|
|
15
|
+
|
|
16
|
+
typedef enum {
|
|
17
|
+
EG_MODE_BLOCK = 0, // refuse EG_REMOTE with ECONNREFUSED
|
|
18
|
+
EG_MODE_LOG = 1, // observe only
|
|
19
|
+
} eg_mode_t;
|
|
20
|
+
|
|
21
|
+
eg_class_t eg_classify(const struct sockaddr *sa, socklen_t len);
|
|
22
|
+
|
|
23
|
+
// "block" or unset selects block; "log" selects log. Anything else is block:
|
|
24
|
+
// an unknown mode must fail closed.
|
|
25
|
+
eg_mode_t eg_parse_mode(const char *value);
|
|
26
|
+
|
|
27
|
+
int eg_should_deny(eg_mode_t mode, eg_class_t cls);
|
|
28
|
+
|
|
29
|
+
// Writes "1.2.3.4:443" or "[2001:db8::1]:443". Returns 0 on success.
|
|
30
|
+
int eg_format_peer(const struct sockaddr *sa, socklen_t len, char *out, size_t n);
|
|
31
|
+
|
|
32
|
+
// Fixed-capacity set of strings, so a retrying client logs a destination once
|
|
33
|
+
// per process instead of once per attempt.
|
|
34
|
+
#define EG_SEEN_CAPACITY 128
|
|
35
|
+
#define EG_SEEN_KEY_LENGTH 192
|
|
36
|
+
typedef struct {
|
|
37
|
+
char keys[EG_SEEN_CAPACITY][EG_SEEN_KEY_LENGTH];
|
|
38
|
+
int count;
|
|
39
|
+
int overflow; // insertions refused because the table was full
|
|
40
|
+
} eg_seen_t;
|
|
41
|
+
|
|
42
|
+
// 1 when the key was not present and was inserted; 0 when already present or
|
|
43
|
+
// when the table is full (counted in overflow).
|
|
44
|
+
int eg_seen_insert(eg_seen_t *seen, const char *key);
|
|
45
|
+
|
|
46
|
+
#define EG_EVENT_PREFIX "eas-egress-guard"
|
|
47
|
+
|
|
48
|
+
// Function name of the one event a process writes when its seen table fills:
|
|
49
|
+
// from then on distinct destinations are still refused but no longer listed.
|
|
50
|
+
#define EG_OVERFLOW_FUNCTION "overflow"
|
|
51
|
+
|
|
52
|
+
// One tab-separated line, newline terminated:
|
|
53
|
+
// eas-egress-guard\t<progname>\t<pid>\t<function>\t<action>\t<peer>\t<caller>,<caller>...\n
|
|
54
|
+
// Tabs and newlines inside fields are replaced with spaces. Returns the length
|
|
55
|
+
// written, or -1 when it does not fit.
|
|
56
|
+
int eg_format_event(char *out, size_t n, const char *progname, int pid, const char *function,
|
|
57
|
+
const char *action, const char *peer, const char *const *callers,
|
|
58
|
+
int caller_count);
|
|
59
|
+
|
|
60
|
+
#endif
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
// Black-box host tests of the built guard, loaded through
|
|
2
|
+
// DYLD_INSERT_LIBRARIES like the simulator does. Remote calls use fd -1 or are
|
|
3
|
+
// refused before the kernel; the only real sockets are on loopback. Build and
|
|
4
|
+
// run with tests/run-guard-tests.sh.
|
|
5
|
+
//
|
|
6
|
+
// guard-insert-test <case> <log-path> [<extra-path>]
|
|
7
|
+
#include <arpa/inet.h>
|
|
8
|
+
#include <dlfcn.h>
|
|
9
|
+
#include <errno.h>
|
|
10
|
+
#include <fcntl.h>
|
|
11
|
+
#include <netinet/in.h>
|
|
12
|
+
#include <stdio.h>
|
|
13
|
+
#include <stdlib.h>
|
|
14
|
+
#include <string.h>
|
|
15
|
+
#include <sys/socket.h>
|
|
16
|
+
#include <sys/stat.h>
|
|
17
|
+
#include <sys/uio.h>
|
|
18
|
+
#include <unistd.h>
|
|
19
|
+
|
|
20
|
+
typedef int (*connect_fn)(int, const struct sockaddr *, socklen_t);
|
|
21
|
+
typedef ssize_t (*sendto_fn)(int, const void *, size_t, int, const struct sockaddr *, socklen_t);
|
|
22
|
+
typedef ssize_t (*sendmsg_fn)(int, const struct msghdr *, int);
|
|
23
|
+
|
|
24
|
+
static struct sockaddr_in remote(int port) {
|
|
25
|
+
struct sockaddr_in a;
|
|
26
|
+
memset(&a, 0, sizeof a);
|
|
27
|
+
a.sin_family = AF_INET;
|
|
28
|
+
a.sin_len = sizeof a;
|
|
29
|
+
a.sin_port = htons(port);
|
|
30
|
+
inet_pton(AF_INET, "192.0.2.1", &a.sin_addr);
|
|
31
|
+
return a;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static struct sockaddr_in6 remote6(int port) {
|
|
35
|
+
struct sockaddr_in6 a;
|
|
36
|
+
memset(&a, 0, sizeof a);
|
|
37
|
+
a.sin6_family = AF_INET6;
|
|
38
|
+
a.sin6_len = sizeof a;
|
|
39
|
+
a.sin6_port = htons(port);
|
|
40
|
+
inet_pton(AF_INET6, "2001:db8::1", &a.sin6_addr);
|
|
41
|
+
return a;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static int refused_sendto(int port) {
|
|
45
|
+
struct sockaddr_in to = remote(port);
|
|
46
|
+
errno = 0;
|
|
47
|
+
return sendto(-1, "x", 1, 0, (struct sockaddr *)&to, sizeof to) == -1 && errno == ECONNREFUSED;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static off_t file_size(const char *path) {
|
|
51
|
+
struct stat st;
|
|
52
|
+
return stat(path, &st) == 0 ? st.st_size : -1;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
static int count_lines(const char *path, const char *needle) {
|
|
56
|
+
FILE *f = fopen(path, "r");
|
|
57
|
+
if (f == NULL) {
|
|
58
|
+
return -1;
|
|
59
|
+
}
|
|
60
|
+
int count = 0;
|
|
61
|
+
char line[1024];
|
|
62
|
+
while (fgets(line, sizeof line, f)) {
|
|
63
|
+
if (strstr(line, needle)) {
|
|
64
|
+
count++;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
fclose(f);
|
|
68
|
+
return count;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Daemon-style cleanup: close every descriptor the process does not know
|
|
72
|
+
// about, then open an application file. The guard's event must land in the
|
|
73
|
+
// event log, not in that file.
|
|
74
|
+
static int fd_reuse(const char *log_path, const char *app_path) {
|
|
75
|
+
for (int fd = 3; fd < getdtablesize(); fd++) {
|
|
76
|
+
close(fd);
|
|
77
|
+
}
|
|
78
|
+
int app_fd = open(app_path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
|
|
79
|
+
if (app_fd < 0 || !refused_sendto(9)) {
|
|
80
|
+
return 2;
|
|
81
|
+
}
|
|
82
|
+
close(app_fd);
|
|
83
|
+
if (file_size(app_path) != 0) {
|
|
84
|
+
printf("FAIL fd-reuse: %lld bytes written into an unrelated file\n",
|
|
85
|
+
(long long)file_size(app_path));
|
|
86
|
+
return 1;
|
|
87
|
+
}
|
|
88
|
+
if (count_lines(log_path, "\tsendto\tblocked\t192.0.2.1:9\t") != 1) {
|
|
89
|
+
printf("FAIL fd-reuse: event missing from the event log\n");
|
|
90
|
+
return 1;
|
|
91
|
+
}
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Started with stdout closed, as the runner arranges: the process's own stdout
|
|
96
|
+
// output must not end up in the event log, and the event must.
|
|
97
|
+
static int low_fd(const char *log_path) {
|
|
98
|
+
printf("STDOUT-LINE\n");
|
|
99
|
+
fflush(stdout);
|
|
100
|
+
if (!refused_sendto(9)) {
|
|
101
|
+
return 2;
|
|
102
|
+
}
|
|
103
|
+
if (count_lines(log_path, "STDOUT-LINE") != 0) {
|
|
104
|
+
return 1;
|
|
105
|
+
}
|
|
106
|
+
return count_lines(log_path, "\tsendto\tblocked\t192.0.2.1:9\t") == 1 ? 0 : 1;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
static int nocancel(void) {
|
|
110
|
+
connect_fn nc_connect = (connect_fn)dlsym(RTLD_DEFAULT, "connect$NOCANCEL");
|
|
111
|
+
sendto_fn nc_sendto = (sendto_fn)dlsym(RTLD_DEFAULT, "sendto$NOCANCEL");
|
|
112
|
+
sendmsg_fn nc_sendmsg = (sendmsg_fn)dlsym(RTLD_DEFAULT, "sendmsg$NOCANCEL");
|
|
113
|
+
if (!nc_connect || !nc_sendto || !nc_sendmsg) {
|
|
114
|
+
return 2;
|
|
115
|
+
}
|
|
116
|
+
struct sockaddr_in to = remote(443);
|
|
117
|
+
int failures = 0;
|
|
118
|
+
errno = 0;
|
|
119
|
+
if (nc_connect(-1, (struct sockaddr *)&to, sizeof to) != -1 || errno != ECONNREFUSED) {
|
|
120
|
+
printf("FAIL nocancel: connect$NOCANCEL not refused (errno %d)\n", errno);
|
|
121
|
+
failures++;
|
|
122
|
+
}
|
|
123
|
+
errno = 0;
|
|
124
|
+
if (nc_sendto(-1, "x", 1, 0, (struct sockaddr *)&to, sizeof to) != -1 || errno != ECONNREFUSED) {
|
|
125
|
+
printf("FAIL nocancel: sendto$NOCANCEL not refused (errno %d)\n", errno);
|
|
126
|
+
failures++;
|
|
127
|
+
}
|
|
128
|
+
char payload[] = "x";
|
|
129
|
+
struct iovec iov = {.iov_base = payload, .iov_len = 1};
|
|
130
|
+
struct msghdr msg = {.msg_name = &to, .msg_namelen = sizeof to, .msg_iov = &iov, .msg_iovlen = 1};
|
|
131
|
+
errno = 0;
|
|
132
|
+
if (nc_sendmsg(-1, &msg, 0) != -1 || errno != ECONNREFUSED) {
|
|
133
|
+
printf("FAIL nocancel: sendmsg$NOCANCEL not refused (errno %d)\n", errno);
|
|
134
|
+
failures++;
|
|
135
|
+
}
|
|
136
|
+
return failures ? 1 : 0;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// The shapes the kernel accepts as IPv4/IPv6 despite sa_family == AF_UNSPEC.
|
|
140
|
+
static int unspec(void) {
|
|
141
|
+
int failures = 0;
|
|
142
|
+
struct sockaddr_in to = remote(443);
|
|
143
|
+
to.sin_family = AF_UNSPEC;
|
|
144
|
+
int tcp = socket(AF_INET, SOCK_STREAM, 0);
|
|
145
|
+
errno = 0;
|
|
146
|
+
if (connect(tcp, (struct sockaddr *)&to, sizeof to) != -1 || errno != ECONNREFUSED) {
|
|
147
|
+
printf("FAIL unspec: TCP connect with AF_UNSPEC not refused (errno %d)\n", errno);
|
|
148
|
+
failures++;
|
|
149
|
+
}
|
|
150
|
+
close(tcp);
|
|
151
|
+
struct sockaddr_in6 to6 = remote6(443);
|
|
152
|
+
to6.sin6_family = AF_UNSPEC;
|
|
153
|
+
int tcp6 = socket(AF_INET6, SOCK_STREAM, 0);
|
|
154
|
+
errno = 0;
|
|
155
|
+
if (connect(tcp6, (struct sockaddr *)&to6, sizeof to6) != -1 || errno != ECONNREFUSED) {
|
|
156
|
+
printf("FAIL unspec: TCP6 connect with AF_UNSPEC not refused (errno %d)\n", errno);
|
|
157
|
+
failures++;
|
|
158
|
+
}
|
|
159
|
+
close(tcp6);
|
|
160
|
+
errno = 0;
|
|
161
|
+
if (sendto(-1, "x", 1, 0, (struct sockaddr *)&to, sizeof to) != -1 || errno != ECONNREFUSED) {
|
|
162
|
+
printf("FAIL unspec: sendto with AF_UNSPEC not refused (errno %d)\n", errno);
|
|
163
|
+
failures++;
|
|
164
|
+
}
|
|
165
|
+
char payload[] = "x";
|
|
166
|
+
struct iovec iov = {.iov_base = payload, .iov_len = 1};
|
|
167
|
+
struct msghdr msg = {.msg_name = &to, .msg_namelen = sizeof to, .msg_iov = &iov, .msg_iovlen = 1};
|
|
168
|
+
errno = 0;
|
|
169
|
+
if (sendmsg(-1, &msg, 0) != -1 || errno != ECONNREFUSED) {
|
|
170
|
+
printf("FAIL unspec: sendmsg with AF_UNSPEC not refused (errno %d)\n", errno);
|
|
171
|
+
failures++;
|
|
172
|
+
}
|
|
173
|
+
return failures ? 1 : 0;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// A UDP socket associated with loopback dissolves the association through
|
|
177
|
+
// connect(AF_UNSPEC), whatever address bytes follow the family. The guard
|
|
178
|
+
// must let the kernel do that.
|
|
179
|
+
static int udp_disconnect(void) {
|
|
180
|
+
int fd = socket(AF_INET, SOCK_DGRAM, 0);
|
|
181
|
+
struct sockaddr_in loopback;
|
|
182
|
+
memset(&loopback, 0, sizeof loopback);
|
|
183
|
+
loopback.sin_family = AF_INET;
|
|
184
|
+
loopback.sin_len = sizeof loopback;
|
|
185
|
+
loopback.sin_port = htons(9);
|
|
186
|
+
loopback.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
187
|
+
if (connect(fd, (struct sockaddr *)&loopback, sizeof loopback) != 0) {
|
|
188
|
+
return 2;
|
|
189
|
+
}
|
|
190
|
+
struct sockaddr_in peer;
|
|
191
|
+
socklen_t peer_len = sizeof peer;
|
|
192
|
+
if (getpeername(fd, (struct sockaddr *)&peer, &peer_len) != 0) {
|
|
193
|
+
return 2;
|
|
194
|
+
}
|
|
195
|
+
int failures = 0;
|
|
196
|
+
for (int stale = 0; stale < 2; stale++) {
|
|
197
|
+
struct sockaddr_in dissolve = stale ? remote(443) : loopback;
|
|
198
|
+
if (!stale) {
|
|
199
|
+
memset(&dissolve, 0, sizeof dissolve);
|
|
200
|
+
dissolve.sin_len = sizeof dissolve;
|
|
201
|
+
}
|
|
202
|
+
dissolve.sin_family = AF_UNSPEC;
|
|
203
|
+
if (connect(fd, (struct sockaddr *)&loopback, sizeof loopback) != 0) {
|
|
204
|
+
return 2;
|
|
205
|
+
}
|
|
206
|
+
errno = 0;
|
|
207
|
+
int rc = connect(fd, (struct sockaddr *)&dissolve, sizeof dissolve);
|
|
208
|
+
int err = errno;
|
|
209
|
+
peer_len = sizeof peer;
|
|
210
|
+
int still_connected = getpeername(fd, (struct sockaddr *)&peer, &peer_len) == 0;
|
|
211
|
+
if (rc != -1 || err != EAFNOSUPPORT || still_connected) {
|
|
212
|
+
printf("FAIL udp-disconnect (%s address): rc=%d errno=%d still_connected=%d\n",
|
|
213
|
+
stale ? "stale remote" : "zeroed", rc, err, still_connected);
|
|
214
|
+
failures++;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
close(fd);
|
|
218
|
+
return failures ? 1 : 0;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Past 128 distinct destinations a process writes one overflow line and
|
|
222
|
+
// nothing more; refusal continues.
|
|
223
|
+
static int overflow(const char *log_path) {
|
|
224
|
+
for (int port = 1; port <= 140; port++) {
|
|
225
|
+
if (!refused_sendto(port)) {
|
|
226
|
+
return 2;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
int listed = count_lines(log_path, "\tsendto\tblocked\t192.0.2.1:");
|
|
230
|
+
int overflow_lines = count_lines(log_path, "\toverflow\tblocked\t128 distinct destinations\t");
|
|
231
|
+
if (listed != 128 || overflow_lines != 1) {
|
|
232
|
+
printf("FAIL overflow: %d destinations listed, %d overflow line(s)\n", listed, overflow_lines);
|
|
233
|
+
return 1;
|
|
234
|
+
}
|
|
235
|
+
return 0;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// The event names the images above the guard, not the guard itself.
|
|
239
|
+
static int callers(const char *log_path) {
|
|
240
|
+
if (!refused_sendto(9)) {
|
|
241
|
+
return 2;
|
|
242
|
+
}
|
|
243
|
+
FILE *f = fopen(log_path, "r");
|
|
244
|
+
if (f == NULL) {
|
|
245
|
+
return 2;
|
|
246
|
+
}
|
|
247
|
+
char line[1024];
|
|
248
|
+
int ok = 0;
|
|
249
|
+
while (fgets(line, sizeof line, f)) {
|
|
250
|
+
if (strstr(line, "\tsendto\tblocked\t192.0.2.1:9\t")) {
|
|
251
|
+
const char *callers_field = strrchr(line, '\t') + 1;
|
|
252
|
+
ok = strstr(callers_field, "guard-insert-test") != NULL &&
|
|
253
|
+
strstr(callers_field, "egress-guard") == NULL;
|
|
254
|
+
if (!ok) {
|
|
255
|
+
printf("FAIL callers: %s", line);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
fclose(f);
|
|
260
|
+
return ok ? 0 : 1;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
int main(int argc, char **argv) {
|
|
264
|
+
if (argc < 3) {
|
|
265
|
+
return 2;
|
|
266
|
+
}
|
|
267
|
+
const char *name = argv[1];
|
|
268
|
+
const char *log_path = argv[2];
|
|
269
|
+
if (strcmp(name, "fd-reuse") == 0) {
|
|
270
|
+
return argc == 4 ? fd_reuse(log_path, argv[3]) : 2;
|
|
271
|
+
}
|
|
272
|
+
if (strcmp(name, "low-fd") == 0) {
|
|
273
|
+
return low_fd(log_path);
|
|
274
|
+
}
|
|
275
|
+
if (strcmp(name, "nocancel") == 0) {
|
|
276
|
+
return nocancel();
|
|
277
|
+
}
|
|
278
|
+
if (strcmp(name, "unspec") == 0) {
|
|
279
|
+
return unspec();
|
|
280
|
+
}
|
|
281
|
+
if (strcmp(name, "udp-disconnect") == 0) {
|
|
282
|
+
return udp_disconnect();
|
|
283
|
+
}
|
|
284
|
+
if (strcmp(name, "callers") == 0) {
|
|
285
|
+
return callers(log_path);
|
|
286
|
+
}
|
|
287
|
+
if (strcmp(name, "overflow") == 0) {
|
|
288
|
+
return overflow(log_path);
|
|
289
|
+
}
|
|
290
|
+
return 2;
|
|
291
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// White-box host tests of guard.c's dedupe lock: the schedules that would
|
|
2
|
+
// abort the process are forced deterministically. Every case runs in its own
|
|
3
|
+
// process; sockets are never opened (fd -1), so nothing can leave the host.
|
|
4
|
+
// Build and run with tests/run-guard-tests.sh.
|
|
5
|
+
#include "../policy.h"
|
|
6
|
+
|
|
7
|
+
#include <pthread.h>
|
|
8
|
+
#include <signal.h>
|
|
9
|
+
#include <sys/wait.h>
|
|
10
|
+
|
|
11
|
+
static int scheduled_seen_insert(eg_seen_t *seen, const char *key);
|
|
12
|
+
#define eg_seen_insert scheduled_seen_insert
|
|
13
|
+
#include "../guard.c"
|
|
14
|
+
#undef eg_seen_insert
|
|
15
|
+
|
|
16
|
+
#include <arpa/inet.h>
|
|
17
|
+
|
|
18
|
+
#define CONTENTION_THREADS 8
|
|
19
|
+
#define CONTENTION_KEYS 12
|
|
20
|
+
#define CONTENTION_ROUNDS 25
|
|
21
|
+
|
|
22
|
+
static int interrupt_insert;
|
|
23
|
+
static int ready_pipe[2];
|
|
24
|
+
static int release_pipe[2];
|
|
25
|
+
|
|
26
|
+
static struct sockaddr_in remote(int port) {
|
|
27
|
+
struct sockaddr_in a;
|
|
28
|
+
memset(&a, 0, sizeof a);
|
|
29
|
+
a.sin_family = AF_INET;
|
|
30
|
+
a.sin_len = sizeof a;
|
|
31
|
+
a.sin_port = htons(port);
|
|
32
|
+
inet_pton(AF_INET, "192.0.2.1", &a.sin_addr);
|
|
33
|
+
return a;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Block mode must refuse before touching the kernel, even on an invalid fd.
|
|
37
|
+
static void send_remote(int port) {
|
|
38
|
+
struct sockaddr_in to = remote(port);
|
|
39
|
+
if (eg_sendto(-1, "x", 1, 0, (struct sockaddr *)&to, sizeof to) != -1 || errno != ECONNREFUSED) {
|
|
40
|
+
_exit(2);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static void on_signal(int sig) {
|
|
45
|
+
(void)sig;
|
|
46
|
+
send_remote(9);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static int scheduled_seen_insert(eg_seen_t *seen, const char *key) {
|
|
50
|
+
if (interrupt_insert) {
|
|
51
|
+
interrupt_insert = 0;
|
|
52
|
+
raise(SIGUSR1);
|
|
53
|
+
}
|
|
54
|
+
return eg_seen_insert(seen, key);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static int signal_reentry(void) {
|
|
58
|
+
signal(SIGUSR1, on_signal);
|
|
59
|
+
interrupt_insert = 1;
|
|
60
|
+
send_remote(9);
|
|
61
|
+
send_remote(10);
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
static void *hold_lock(void *unused) {
|
|
66
|
+
(void)unused;
|
|
67
|
+
char byte = 'x';
|
|
68
|
+
os_unfair_lock_lock(&eg_lock);
|
|
69
|
+
(void)write(ready_pipe[1], &byte, 1);
|
|
70
|
+
(void)read(release_pipe[0], &byte, 1);
|
|
71
|
+
os_unfair_lock_unlock(&eg_lock);
|
|
72
|
+
return NULL;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
static int fork_while_locked(void) {
|
|
76
|
+
if (pipe(ready_pipe) || pipe(release_pipe)) {
|
|
77
|
+
return 2;
|
|
78
|
+
}
|
|
79
|
+
pthread_t thread;
|
|
80
|
+
if (pthread_create(&thread, NULL, hold_lock, NULL)) {
|
|
81
|
+
return 2;
|
|
82
|
+
}
|
|
83
|
+
char byte;
|
|
84
|
+
if (read(ready_pipe[0], &byte, 1) != 1) {
|
|
85
|
+
return 2;
|
|
86
|
+
}
|
|
87
|
+
pid_t child = fork();
|
|
88
|
+
if (child < 0) {
|
|
89
|
+
return 2;
|
|
90
|
+
}
|
|
91
|
+
if (child == 0) {
|
|
92
|
+
alarm(5);
|
|
93
|
+
send_remote(9);
|
|
94
|
+
send_remote(10);
|
|
95
|
+
_exit(0);
|
|
96
|
+
}
|
|
97
|
+
(void)write(release_pipe[1], "x", 1);
|
|
98
|
+
pthread_join(thread, NULL);
|
|
99
|
+
int status;
|
|
100
|
+
waitpid(child, &status, 0);
|
|
101
|
+
if (WIFSIGNALED(status)) {
|
|
102
|
+
printf("FAIL fork: child terminated by signal %d\n", WTERMSIG(status));
|
|
103
|
+
return 1;
|
|
104
|
+
}
|
|
105
|
+
return WIFEXITED(status) ? WEXITSTATUS(status) : 2;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
static void *hammer(void *unused) {
|
|
109
|
+
(void)unused;
|
|
110
|
+
for (int round = 0; round < CONTENTION_ROUNDS; round++) {
|
|
111
|
+
for (int key = 0; key < CONTENTION_KEYS; key++) {
|
|
112
|
+
send_remote(1000 + key);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return NULL;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Under contention a thread may give up its insert; the next attempt on the
|
|
119
|
+
// same key must then record it, so every key ends up logged exactly once.
|
|
120
|
+
static int contention(const char *log_path) {
|
|
121
|
+
pthread_t threads[CONTENTION_THREADS];
|
|
122
|
+
for (int i = 0; i < CONTENTION_THREADS; i++) {
|
|
123
|
+
if (pthread_create(&threads[i], NULL, hammer, NULL)) {
|
|
124
|
+
return 2;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
for (int i = 0; i < CONTENTION_THREADS; i++) {
|
|
128
|
+
pthread_join(threads[i], NULL);
|
|
129
|
+
}
|
|
130
|
+
FILE *log = fopen(log_path, "r");
|
|
131
|
+
if (log == NULL) {
|
|
132
|
+
return 2;
|
|
133
|
+
}
|
|
134
|
+
int counts[CONTENTION_KEYS] = {0};
|
|
135
|
+
int total = 0;
|
|
136
|
+
char line[1024];
|
|
137
|
+
while (fgets(line, sizeof line, log)) {
|
|
138
|
+
total++;
|
|
139
|
+
for (int key = 0; key < CONTENTION_KEYS; key++) {
|
|
140
|
+
char needle[64];
|
|
141
|
+
snprintf(needle, sizeof needle, "\tsendto\tblocked\t192.0.2.1:%d\t", 1000 + key);
|
|
142
|
+
if (strstr(line, needle)) {
|
|
143
|
+
counts[key]++;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
fclose(log);
|
|
148
|
+
int failures = 0;
|
|
149
|
+
for (int key = 0; key < CONTENTION_KEYS; key++) {
|
|
150
|
+
if (counts[key] != 1) {
|
|
151
|
+
printf("FAIL contention: 192.0.2.1:%d logged %d times\n", 1000 + key, counts[key]);
|
|
152
|
+
failures++;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (total != CONTENTION_KEYS) {
|
|
156
|
+
printf("FAIL contention: %d lines for %d keys\n", total, CONTENTION_KEYS);
|
|
157
|
+
failures++;
|
|
158
|
+
}
|
|
159
|
+
return failures ? 1 : 0;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
int main(int argc, char **argv) {
|
|
163
|
+
if (argc != 2) {
|
|
164
|
+
return 2;
|
|
165
|
+
}
|
|
166
|
+
eg_mode = EG_MODE_BLOCK;
|
|
167
|
+
if (strcmp(argv[1], "signal") == 0) {
|
|
168
|
+
return signal_reentry();
|
|
169
|
+
}
|
|
170
|
+
if (strcmp(argv[1], "fork") == 0) {
|
|
171
|
+
return fork_while_locked();
|
|
172
|
+
}
|
|
173
|
+
if (strcmp(argv[1], "contention") == 0) {
|
|
174
|
+
return contention(eg_log_path);
|
|
175
|
+
}
|
|
176
|
+
return 2;
|
|
177
|
+
}
|