endpoint_security 0.1.0-universal-darwin

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 (49) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +44 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +114 -0
  5. data/Rakefile +170 -0
  6. data/codegen/ast.rb +67 -0
  7. data/codegen/emit_c.rb +68 -0
  8. data/codegen/emit_ruby.rb +128 -0
  9. data/codegen/ir.rb +193 -0
  10. data/codegen/overlay/availability.yml +158 -0
  11. data/codegen/overlay/version_map.yml +92 -0
  12. data/codegen/run.rb +80 -0
  13. data/codegen/snapshots/26.5.json +5828 -0
  14. data/examples/eslogger_clone.rb +5 -0
  15. data/examples/execblock.rb +16 -0
  16. data/examples/filemon.rb +10 -0
  17. data/examples/procmon.rb +13 -0
  18. data/ext/endpoint_security/client.c +795 -0
  19. data/ext/endpoint_security/client.h +88 -0
  20. data/ext/endpoint_security/endpoint_security.c +17 -0
  21. data/ext/endpoint_security/extconf.rb +37 -0
  22. data/ext/endpoint_security/field.c +719 -0
  23. data/ext/endpoint_security/field.h +42 -0
  24. data/ext/endpoint_security/generated/es_schema.c +1233 -0
  25. data/ext/endpoint_security/message.c +426 -0
  26. data/ext/endpoint_security/message.h +13 -0
  27. data/ext/endpoint_security/mute.c +291 -0
  28. data/ext/endpoint_security/mute.h +8 -0
  29. data/ext/endpoint_security/queue.c +124 -0
  30. data/ext/endpoint_security/queue.h +46 -0
  31. data/ext/endpoint_security/watchdog.c +243 -0
  32. data/lib/endpoint_security/availability.rb +30 -0
  33. data/lib/endpoint_security/client.rb +280 -0
  34. data/lib/endpoint_security/diagnostics.rb +26 -0
  35. data/lib/endpoint_security/doctor.rb +35 -0
  36. data/lib/endpoint_security/errors.rb +42 -0
  37. data/lib/endpoint_security/generated/availability.rb +169 -0
  38. data/lib/endpoint_security/generated/enums.rb +409 -0
  39. data/lib/endpoint_security/generated/event_types.rb +520 -0
  40. data/lib/endpoint_security/message.rb +29 -0
  41. data/lib/endpoint_security/object_model.rb +170 -0
  42. data/lib/endpoint_security/recorder.rb +26 -0
  43. data/lib/endpoint_security/version.rb +6 -0
  44. data/lib/endpoint_security.rb +38 -0
  45. data/support/entitlements.plist +8 -0
  46. data/support/esmock/esmock.c +482 -0
  47. data/support/esmock/esmock.h +21 -0
  48. data/support/sign_ruby.sh +13 -0
  49. metadata +90 -0
@@ -0,0 +1,291 @@
1
+ /* mute.c
2
+ * Calling thread: Ruby control/dispatcher threads with the GVL.
3
+ * Returned ES-owned mute sets are copied before their mandatory release.
4
+ */
5
+ #include "mute.h"
6
+
7
+ #include <EndpointSecurity/EndpointSecurity.h>
8
+ #include <bsm/libbsm.h>
9
+ #include <limits.h>
10
+ #include <mach/mach.h>
11
+ #include <stdint.h>
12
+ #include <unistd.h>
13
+
14
+ #include "client.h"
15
+ #include "field.h"
16
+
17
+ static esrb_client_t *
18
+ mute_client(VALUE self)
19
+ {
20
+ esrb_client_t *client;
21
+ TypedData_Get_Struct(self, esrb_client_t, &esrb_client_type, client);
22
+ if (client->owner_pid != getpid()) {
23
+ rb_raise(rb_path2class("EndpointSecurity::ForkedClientError"), "Endpoint Security clients cannot be used after fork");
24
+ }
25
+ if (atomic_load_explicit(&client->closed, memory_order_acquire)) {
26
+ rb_raise(rb_path2class("EndpointSecurity::ClientError"), "client is closed");
27
+ }
28
+ return client;
29
+ }
30
+
31
+ static audit_token_t
32
+ audit_token_from_value(VALUE value)
33
+ {
34
+ audit_token_t token = {0};
35
+ token.val[0] = NUM2UINT(rb_funcall(value, rb_intern("auid"), 0));
36
+ token.val[1] = NUM2UINT(rb_funcall(value, rb_intern("euid"), 0));
37
+ token.val[2] = NUM2UINT(rb_funcall(value, rb_intern("egid"), 0));
38
+ token.val[3] = NUM2UINT(rb_funcall(value, rb_intern("ruid"), 0));
39
+ token.val[4] = NUM2UINT(rb_funcall(value, rb_intern("rgid"), 0));
40
+ token.val[5] = NUM2UINT(rb_funcall(value, rb_intern("pid"), 0));
41
+ token.val[6] = NUM2UINT(rb_funcall(value, rb_intern("asid"), 0));
42
+ token.val[7] = NUM2UINT(rb_funcall(value, rb_intern("pidversion"), 0));
43
+ return token;
44
+ }
45
+
46
+ static VALUE
47
+ audit_token_value(audit_token_t token)
48
+ {
49
+ VALUE arguments[] = {
50
+ UINT2NUM(audit_token_to_pid(token)), UINT2NUM(audit_token_to_pidversion(token)),
51
+ UINT2NUM(audit_token_to_ruid(token)), UINT2NUM(audit_token_to_euid(token)),
52
+ UINT2NUM(audit_token_to_rgid(token)), UINT2NUM(audit_token_to_egid(token)),
53
+ UINT2NUM(audit_token_to_asid(token)), UINT2NUM(audit_token_to_auid(token))
54
+ };
55
+ return rb_funcallv(rb_path2class("EndpointSecurity::AuditToken"), rb_intern("__native_new"), 8, arguments);
56
+ }
57
+
58
+ static es_event_type_t *
59
+ event_values(VALUE values, size_t *count, VALUE *storage)
60
+ {
61
+ Check_Type(values, T_ARRAY);
62
+ *count = (size_t)RARRAY_LEN(values);
63
+ if (*count == 0) {
64
+ *storage = Qnil;
65
+ return NULL;
66
+ }
67
+ if (*count > LONG_MAX / sizeof(es_event_type_t)) {
68
+ rb_raise(rb_eArgError, "too many events");
69
+ }
70
+ *storage = rb_str_new(NULL, (long)(*count * sizeof(es_event_type_t)));
71
+ es_event_type_t *events = (es_event_type_t *)RSTRING_PTR(*storage);
72
+ for (size_t index = 0; index < *count; index++) {
73
+ events[index] = (es_event_type_t)NUM2INT(rb_ary_entry(values, (long)index));
74
+ }
75
+ return events;
76
+ }
77
+
78
+ static void
79
+ check_mute_result(es_return_t result)
80
+ {
81
+ if (result != ES_RETURN_SUCCESS) {
82
+ rb_raise(rb_path2class("EndpointSecurity::MuteError"), "Endpoint Security mute operation failed");
83
+ }
84
+ }
85
+
86
+ static VALUE
87
+ mute_path(VALUE self, VALUE action, VALUE path, VALUE type_value, VALUE values)
88
+ {
89
+ esrb_client_t *client = mute_client(self);
90
+ ID action_id = SYM2ID(action);
91
+ es_mute_path_type_t type = (es_mute_path_type_t)NUM2INT(type_value);
92
+ const char *path_string = StringValueCStr(path);
93
+ size_t count;
94
+ VALUE storage;
95
+ es_event_type_t *events = event_values(values, &count, &storage);
96
+ es_return_t result;
97
+ if (action_id == rb_intern("mute")) {
98
+ result = count == 0 ? es_mute_path(client->client, path_string, type)
99
+ : es_mute_path_events(client->client, path_string, type, events, count);
100
+ } else {
101
+ result = count == 0 ? es_unmute_path(client->client, path_string, type)
102
+ : es_unmute_path_events(client->client, path_string, type, events, count);
103
+ }
104
+ RB_GC_GUARD(storage);
105
+ check_mute_result(result);
106
+ return Qtrue;
107
+ }
108
+
109
+ static VALUE
110
+ mute_process(VALUE self, VALUE action, VALUE token_value, VALUE values)
111
+ {
112
+ esrb_client_t *client = mute_client(self);
113
+ audit_token_t token = audit_token_from_value(token_value);
114
+ size_t count;
115
+ VALUE storage;
116
+ es_event_type_t *events = event_values(values, &count, &storage);
117
+ es_return_t result;
118
+ if (SYM2ID(action) == rb_intern("mute")) {
119
+ result = count == 0 ? es_mute_process(client->client, &token)
120
+ : es_mute_process_events(client->client, &token, events, count);
121
+ } else {
122
+ result = count == 0 ? es_unmute_process(client->client, &token)
123
+ : es_unmute_process_events(client->client, &token, events, count);
124
+ }
125
+ RB_GC_GUARD(storage);
126
+ check_mute_result(result);
127
+ return Qtrue;
128
+ }
129
+
130
+ static VALUE
131
+ unmute_all(VALUE self, VALUE target)
132
+ {
133
+ esrb_client_t *client = mute_client(self);
134
+ check_mute_result(RTEST(target) ? es_unmute_all_target_paths(client->client) : es_unmute_all_paths(client->client));
135
+ return Qtrue;
136
+ }
137
+
138
+ static VALUE
139
+ invert_muting(VALUE self, VALUE type)
140
+ {
141
+ esrb_client_t *client = mute_client(self);
142
+ check_mute_result(es_invert_muting(client->client, (es_mute_inversion_type_t)NUM2INT(type)));
143
+ return Qtrue;
144
+ }
145
+
146
+ static VALUE
147
+ muting_inverted(VALUE self, VALUE type)
148
+ {
149
+ esrb_client_t *client = mute_client(self);
150
+ es_mute_inverted_return_t result = es_muting_inverted(client->client, (es_mute_inversion_type_t)NUM2INT(type));
151
+ if (result == ES_MUTE_INVERTED_ERROR) {
152
+ rb_raise(rb_path2class("EndpointSecurity::MuteError"), "failed to query mute inversion state");
153
+ }
154
+ return result == ES_MUTE_INVERTED ? Qtrue : Qfalse;
155
+ }
156
+
157
+ static VALUE
158
+ clear_cache(VALUE self)
159
+ {
160
+ es_clear_cache_result_t result = es_clear_cache(mute_client(self)->client);
161
+ if (result != ES_CLEAR_CACHE_RESULT_SUCCESS) {
162
+ rb_raise(rb_path2class("EndpointSecurity::ClientError"), "failed to clear Endpoint Security cache (%d)", result);
163
+ }
164
+ return Qtrue;
165
+ }
166
+
167
+ static VALUE
168
+ event_symbols(const es_event_type_t *events, size_t count)
169
+ {
170
+ VALUE result = rb_ary_new_capa((long)count);
171
+ VALUE event_type = rb_path2class("EndpointSecurity::EventType");
172
+ for (size_t index = 0; index < count; index++) {
173
+ rb_ary_push(result, rb_funcall(event_type, rb_intern("symbol"), 1, INT2NUM(events[index])));
174
+ }
175
+ return result;
176
+ }
177
+
178
+ static VALUE
179
+ copy_muted_paths(VALUE pointer_value)
180
+ {
181
+ es_muted_paths_t *paths = (es_muted_paths_t *)(uintptr_t)NUM2ULL(pointer_value);
182
+ if (paths->count > LONG_MAX) {
183
+ rb_raise(rb_eRangeError, "muted path count is too large");
184
+ }
185
+ VALUE result = rb_ary_new_capa((long)paths->count);
186
+ for (size_t index = 0; index < paths->count; index++) {
187
+ const es_muted_path_t *path = &paths->paths[index];
188
+ VALUE item = rb_hash_new();
189
+ rb_hash_aset(item, ID2SYM(rb_intern("type")), INT2NUM(path->type));
190
+ rb_hash_aset(item, ID2SYM(rb_intern("path")), esrb_string_token_value(&path->path));
191
+ rb_hash_aset(item, ID2SYM(rb_intern("events")), event_symbols(path->events, path->event_count));
192
+ rb_ary_push(result, item);
193
+ }
194
+ return result;
195
+ }
196
+
197
+ static VALUE
198
+ release_muted_paths(VALUE pointer_value)
199
+ {
200
+ es_release_muted_paths((es_muted_paths_t *)(uintptr_t)NUM2ULL(pointer_value));
201
+ return Qnil;
202
+ }
203
+
204
+ static VALUE
205
+ muted_paths(VALUE self)
206
+ {
207
+ es_muted_paths_t *paths = NULL;
208
+ check_mute_result(es_muted_paths_events(mute_client(self)->client, &paths));
209
+ if (paths == NULL) {
210
+ return rb_ary_new();
211
+ }
212
+ VALUE pointer_value = ULL2NUM((uintptr_t)paths);
213
+ return rb_ensure(copy_muted_paths, pointer_value, release_muted_paths, pointer_value);
214
+ }
215
+
216
+ static VALUE
217
+ copy_muted_processes(VALUE pointer_value)
218
+ {
219
+ es_muted_processes_t *processes = (es_muted_processes_t *)(uintptr_t)NUM2ULL(pointer_value);
220
+ if (processes->count > LONG_MAX) {
221
+ rb_raise(rb_eRangeError, "muted process count is too large");
222
+ }
223
+ VALUE result = rb_ary_new_capa((long)processes->count);
224
+ for (size_t index = 0; index < processes->count; index++) {
225
+ const es_muted_process_t *process = &processes->processes[index];
226
+ VALUE item = rb_hash_new();
227
+ rb_hash_aset(item, ID2SYM(rb_intern("audit_token")), audit_token_value(process->audit_token));
228
+ rb_hash_aset(item, ID2SYM(rb_intern("events")), event_symbols(process->events, process->event_count));
229
+ rb_ary_push(result, item);
230
+ }
231
+ return result;
232
+ }
233
+
234
+ static VALUE
235
+ release_muted_processes(VALUE pointer_value)
236
+ {
237
+ es_release_muted_processes((es_muted_processes_t *)(uintptr_t)NUM2ULL(pointer_value));
238
+ return Qnil;
239
+ }
240
+
241
+ static VALUE
242
+ muted_processes(VALUE self)
243
+ {
244
+ es_muted_processes_t *processes = NULL;
245
+ check_mute_result(es_muted_processes_events(mute_client(self)->client, &processes));
246
+ if (processes == NULL) {
247
+ return rb_ary_new();
248
+ }
249
+ VALUE pointer_value = ULL2NUM((uintptr_t)processes);
250
+ return rb_ensure(copy_muted_processes, pointer_value, release_muted_processes, pointer_value);
251
+ }
252
+
253
+ static VALUE
254
+ audit_token_for_pid(VALUE self, VALUE pid_value)
255
+ {
256
+ (void)self;
257
+ pid_t pid = NUM2PIDT(pid_value);
258
+ mach_port_t task = mach_task_self();
259
+ bool release_task = false;
260
+ if (pid != getpid()) {
261
+ kern_return_t result = task_for_pid(mach_task_self(), pid, &task);
262
+ if (result != KERN_SUCCESS) {
263
+ rb_raise(rb_eArgError, "could not obtain task port for pid %d", pid);
264
+ }
265
+ release_task = true;
266
+ }
267
+ audit_token_t token;
268
+ mach_msg_type_number_t count = TASK_AUDIT_TOKEN_COUNT;
269
+ kern_return_t result = task_info(task, TASK_AUDIT_TOKEN, (task_info_t)&token, &count);
270
+ if (release_task) {
271
+ mach_port_deallocate(mach_task_self(), task);
272
+ }
273
+ if (result != KERN_SUCCESS) {
274
+ rb_raise(rb_eArgError, "could not obtain audit token for pid %d", pid);
275
+ }
276
+ return audit_token_value(token);
277
+ }
278
+
279
+ void
280
+ esrb_init_mute(VALUE client_class)
281
+ {
282
+ rb_define_method(client_class, "__mute_path", mute_path, 4);
283
+ rb_define_method(client_class, "__mute_process", mute_process, 3);
284
+ rb_define_method(client_class, "__unmute_all_paths", unmute_all, 1);
285
+ rb_define_method(client_class, "__invert_muting", invert_muting, 1);
286
+ rb_define_method(client_class, "__muting_inverted", muting_inverted, 1);
287
+ rb_define_method(client_class, "__clear_cache", clear_cache, 0);
288
+ rb_define_method(client_class, "__muted_paths", muted_paths, 0);
289
+ rb_define_method(client_class, "__muted_processes", muted_processes, 0);
290
+ rb_define_method(client_class, "__audit_token_for_pid", audit_token_for_pid, 1);
291
+ }
@@ -0,0 +1,8 @@
1
+ #ifndef ESRB_MUTE_H
2
+ #define ESRB_MUTE_H
3
+
4
+ #include <ruby.h>
5
+
6
+ void esrb_init_mute(VALUE client_class);
7
+
8
+ #endif
@@ -0,0 +1,124 @@
1
+ /* queue.c
2
+ * Calling threads: producer = ES handler (no GVL), consumer = Ruby dispatcher (GVL).
3
+ * The watchdog reads occupied slots without taking a lock.
4
+ */
5
+ #include "queue.h"
6
+
7
+ #include <sched.h>
8
+ #include <stdlib.h>
9
+
10
+ bool
11
+ esrb_queue_init(esrb_queue_t *queue, size_t capacity)
12
+ {
13
+ if (capacity < 2 || (capacity & (capacity - 1)) != 0) {
14
+ return false;
15
+ }
16
+
17
+ queue->slots = calloc(capacity, sizeof(*queue->slots));
18
+ if (queue->slots == NULL) {
19
+ return false;
20
+ }
21
+
22
+ queue->capacity = capacity;
23
+ queue->mask = capacity - 1;
24
+ atomic_init(&queue->enqueue_position, 0);
25
+ atomic_init(&queue->dequeue_position, 0);
26
+ atomic_init(&queue->depth_max, 0);
27
+ for (size_t index = 0; index < capacity; index++) {
28
+ atomic_init(&queue->slots[index].sequence, index);
29
+ atomic_init(&queue->slots[index].occupied, false);
30
+ atomic_init(&queue->slots[index].answer_state, ESRB_ANSWER_NOT_AUTH);
31
+ atomic_init(&queue->slots[index].readers, 0);
32
+ queue->slots[index].watchdog_index = SIZE_MAX;
33
+ }
34
+ return true;
35
+ }
36
+
37
+ void
38
+ esrb_queue_destroy(esrb_queue_t *queue)
39
+ {
40
+ free(queue->slots);
41
+ queue->slots = NULL;
42
+ }
43
+
44
+ esrb_slot_t *
45
+ esrb_queue_enqueue(esrb_queue_t *queue)
46
+ {
47
+ size_t position = atomic_load_explicit(&queue->enqueue_position, memory_order_relaxed);
48
+
49
+ for (;;) {
50
+ esrb_slot_t *slot = &queue->slots[position & queue->mask];
51
+ size_t sequence = atomic_load_explicit(&slot->sequence, memory_order_acquire);
52
+ intptr_t difference = (intptr_t)sequence - (intptr_t)position;
53
+
54
+ if (difference == 0) {
55
+ if (atomic_compare_exchange_weak_explicit(
56
+ &queue->enqueue_position, &position, position + 1, memory_order_relaxed, memory_order_relaxed)) {
57
+ slot->position = position;
58
+ return slot;
59
+ }
60
+ } else if (difference < 0) {
61
+ return NULL;
62
+ } else {
63
+ position = atomic_load_explicit(&queue->enqueue_position, memory_order_relaxed);
64
+ }
65
+ }
66
+ }
67
+
68
+ void
69
+ esrb_queue_publish(esrb_queue_t *queue, esrb_slot_t *slot)
70
+ {
71
+ size_t depth = slot->position + 1 - atomic_load_explicit(&queue->dequeue_position, memory_order_relaxed);
72
+ size_t maximum = atomic_load_explicit(&queue->depth_max, memory_order_relaxed);
73
+ while (depth > maximum && !atomic_compare_exchange_weak_explicit(
74
+ &queue->depth_max, &maximum, depth, memory_order_relaxed, memory_order_relaxed)) {
75
+ }
76
+ atomic_store_explicit(&slot->sequence, slot->position + 1, memory_order_release);
77
+ }
78
+
79
+ esrb_slot_t *
80
+ esrb_queue_dequeue(esrb_queue_t *queue)
81
+ {
82
+ size_t position = atomic_load_explicit(&queue->dequeue_position, memory_order_relaxed);
83
+
84
+ for (;;) {
85
+ esrb_slot_t *slot = &queue->slots[position & queue->mask];
86
+ size_t sequence = atomic_load_explicit(&slot->sequence, memory_order_acquire);
87
+ intptr_t difference = (intptr_t)sequence - (intptr_t)(position + 1);
88
+
89
+ if (difference == 0) {
90
+ if (atomic_compare_exchange_weak_explicit(
91
+ &queue->dequeue_position, &position, position + 1, memory_order_relaxed, memory_order_relaxed)) {
92
+ return slot;
93
+ }
94
+ } else if (difference < 0) {
95
+ return NULL;
96
+ } else {
97
+ position = atomic_load_explicit(&queue->dequeue_position, memory_order_relaxed);
98
+ }
99
+ }
100
+ }
101
+
102
+ void
103
+ esrb_queue_disarm(esrb_slot_t *slot)
104
+ {
105
+ atomic_store_explicit(&slot->occupied, false, memory_order_release);
106
+ while (atomic_load_explicit(&slot->readers, memory_order_acquire) != 0) {
107
+ sched_yield();
108
+ }
109
+ }
110
+
111
+ void
112
+ esrb_queue_release(esrb_queue_t *queue, esrb_slot_t *slot)
113
+ {
114
+ esrb_queue_disarm(slot);
115
+ atomic_store_explicit(&slot->sequence, slot->position + queue->capacity, memory_order_release);
116
+ }
117
+
118
+ size_t
119
+ esrb_queue_depth(const esrb_queue_t *queue)
120
+ {
121
+ size_t enqueued = atomic_load_explicit(&queue->enqueue_position, memory_order_relaxed);
122
+ size_t dequeued = atomic_load_explicit(&queue->dequeue_position, memory_order_relaxed);
123
+ return enqueued - dequeued;
124
+ }
@@ -0,0 +1,46 @@
1
+ #ifndef ESRB_QUEUE_H
2
+ #define ESRB_QUEUE_H
3
+
4
+ #include <EndpointSecurity/EndpointSecurity.h>
5
+ #include <stdbool.h>
6
+ #include <stdatomic.h>
7
+ #include <stddef.h>
8
+ #include <stdint.h>
9
+
10
+ enum {
11
+ ESRB_ANSWER_PENDING = 0,
12
+ ESRB_ANSWER_ANSWERED = 1,
13
+ ESRB_ANSWER_NOT_AUTH = 2
14
+ };
15
+
16
+ typedef struct {
17
+ _Atomic size_t sequence;
18
+ _Atomic bool occupied;
19
+ const es_message_t *message;
20
+ es_client_t *client;
21
+ size_t position;
22
+ uint64_t fire_at;
23
+ _Atomic uint32_t answer_state;
24
+ _Atomic uint32_t readers;
25
+ size_t watchdog_index;
26
+ } esrb_slot_t;
27
+
28
+ typedef struct {
29
+ size_t capacity;
30
+ size_t mask;
31
+ esrb_slot_t *slots;
32
+ _Atomic size_t enqueue_position;
33
+ _Atomic size_t dequeue_position;
34
+ _Atomic size_t depth_max;
35
+ } esrb_queue_t;
36
+
37
+ bool esrb_queue_init(esrb_queue_t *queue, size_t capacity);
38
+ void esrb_queue_destroy(esrb_queue_t *queue);
39
+ esrb_slot_t *esrb_queue_enqueue(esrb_queue_t *queue);
40
+ void esrb_queue_publish(esrb_queue_t *queue, esrb_slot_t *slot);
41
+ esrb_slot_t *esrb_queue_dequeue(esrb_queue_t *queue);
42
+ void esrb_queue_disarm(esrb_slot_t *slot);
43
+ void esrb_queue_release(esrb_queue_t *queue, esrb_slot_t *slot);
44
+ size_t esrb_queue_depth(const esrb_queue_t *queue);
45
+
46
+ #endif