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,795 @@
1
+ /* client.c
2
+ * Calling threads: lifecycle/control = Ruby threads with the GVL; native lifecycle/watchdog and ES handler = no GVL.
3
+ */
4
+ #include "client.h"
5
+
6
+ #include <errno.h>
7
+ #include <fcntl.h>
8
+ #include <limits.h>
9
+ #include <mach/mach_time.h>
10
+ #include <math.h>
11
+ #include <sched.h>
12
+ #include <stdint.h>
13
+ #include <stdlib.h>
14
+ #include <unistd.h>
15
+
16
+ #include "message.h"
17
+ #include "mute.h"
18
+ #ifdef ESRB_MOCK
19
+ #include "esmock.h"
20
+ #include "field.h"
21
+ #endif
22
+
23
+ static VALUE c_client;
24
+
25
+ static void handle_message(esrb_client_t *client, es_client_t *native_client, const es_message_t *message);
26
+
27
+ es_respond_result_t
28
+ esrb_send_response(esrb_client_t *client, es_client_t *native_client, const es_message_t *message,
29
+ es_auth_result_t result, uint32_t flags, bool cache)
30
+ {
31
+ es_respond_result_t response = message->event_type == ES_EVENT_TYPE_AUTH_OPEN
32
+ ? es_respond_flags_result(native_client, message, flags, cache)
33
+ : es_respond_auth_result(native_client, message, result, cache);
34
+ if (response != ES_RESPOND_RESULT_SUCCESS) {
35
+ atomic_fetch_add_explicit(&client->response_errors, 1, memory_order_relaxed);
36
+ }
37
+ return response;
38
+ }
39
+
40
+ static void *
41
+ client_native_main(void *argument)
42
+ {
43
+ esrb_client_t *client = argument;
44
+ es_new_client_result_t result = es_new_client(&client->client, ^(es_client_t *native, const es_message_t *message) {
45
+ handle_message(client, native, message);
46
+ });
47
+
48
+ pthread_mutex_lock(&client->watchdog.mutex);
49
+ atomic_store_explicit(&client->creation_result, result, memory_order_relaxed);
50
+ atomic_store_explicit(&client->client_ready, true, memory_order_release);
51
+ pthread_cond_broadcast(&client->watchdog.condition);
52
+ pthread_mutex_unlock(&client->watchdog.mutex);
53
+ if (result != ES_NEW_CLIENT_RESULT_SUCCESS) {
54
+ return NULL;
55
+ }
56
+
57
+ esrb_watchdog_main(client);
58
+ atomic_store_explicit(&client->watchdog_stopped, true, memory_order_release);
59
+
60
+ pthread_mutex_lock(&client->watchdog.mutex);
61
+ while (!atomic_load_explicit(&client->delete_ready, memory_order_acquire)) {
62
+ pthread_cond_wait(&client->watchdog.condition, &client->watchdog.mutex);
63
+ }
64
+ pthread_mutex_unlock(&client->watchdog.mutex);
65
+ if (es_delete_client(client->client) != ES_RETURN_SUCCESS) {
66
+ atomic_fetch_add_explicit(&client->delete_errors, 1, memory_order_relaxed);
67
+ }
68
+ return NULL;
69
+ }
70
+
71
+ static void
72
+ client_answer_slot(esrb_client_t *client, esrb_slot_t *slot)
73
+ {
74
+ uint32_t expected = ESRB_ANSWER_PENDING;
75
+ if (!atomic_compare_exchange_strong_explicit(
76
+ &slot->answer_state, &expected, ESRB_ANSWER_ANSWERED, memory_order_acq_rel, memory_order_acquire)) {
77
+ return;
78
+ }
79
+ esrb_send_response(client, slot->client, slot->message, client->default_auth,
80
+ client->default_auth == ES_AUTH_RESULT_ALLOW ? UINT32_MAX : 0, client->default_cache);
81
+ }
82
+
83
+ static void
84
+ client_answer_occupied(esrb_client_t *client)
85
+ {
86
+ for (size_t index = 0; index < client->queue.capacity; index++) {
87
+ esrb_slot_t *slot = &client->queue.slots[index];
88
+ if (atomic_load_explicit(&slot->occupied, memory_order_acquire)) {
89
+ client_answer_slot(client, slot);
90
+ }
91
+ }
92
+ }
93
+
94
+ static void
95
+ client_drain_and_release(esrb_client_t *client)
96
+ {
97
+ esrb_slot_t *slot;
98
+ while ((slot = esrb_queue_dequeue(&client->queue)) != NULL) {
99
+ client_answer_slot(client, slot);
100
+ es_release_message(slot->message);
101
+ esrb_queue_release(&client->queue, slot);
102
+ }
103
+ }
104
+
105
+ static void
106
+ client_close_native(esrb_client_t *client)
107
+ {
108
+ if (atomic_exchange_explicit(&client->closed, true, memory_order_acq_rel)) {
109
+ return;
110
+ }
111
+
112
+ if (client->client != NULL) {
113
+ es_unsubscribe_all(client->client);
114
+ }
115
+ while (atomic_load_explicit(&client->active_callbacks, memory_order_acquire) != 0) {
116
+ sched_yield();
117
+ }
118
+ if (client->watchdog_thread_started) {
119
+ atomic_store_explicit(&client->watchdog_running, false, memory_order_release);
120
+ pthread_cond_broadcast(&client->watchdog.condition);
121
+ while (!atomic_load_explicit(&client->watchdog_stopped, memory_order_acquire)) {
122
+ sched_yield();
123
+ }
124
+ }
125
+ client_answer_occupied(client);
126
+ client_drain_and_release(client);
127
+ if (client->watchdog_thread_started) {
128
+ pthread_mutex_lock(&client->watchdog.mutex);
129
+ atomic_store_explicit(&client->delete_ready, true, memory_order_release);
130
+ pthread_cond_broadcast(&client->watchdog.condition);
131
+ pthread_mutex_unlock(&client->watchdog.mutex);
132
+ pthread_join(client->watchdog_thread, NULL);
133
+ client->watchdog_thread_started = false;
134
+ client->client = NULL;
135
+ }
136
+ esrb_notify(client);
137
+ }
138
+
139
+ static void
140
+ client_free(void *pointer)
141
+ {
142
+ esrb_client_t *client = pointer;
143
+ bool owned_here = client->owner_pid == 0 || client->owner_pid == getpid();
144
+ if (owned_here) {
145
+ client_close_native(client);
146
+ } else {
147
+ client->client = NULL;
148
+ atomic_store_explicit(&client->watchdog_running, false, memory_order_release);
149
+ atomic_store_explicit(&client->closed, true, memory_order_release);
150
+ }
151
+ esrb_queue_destroy(&client->queue);
152
+ if (owned_here) {
153
+ esrb_watchdog_destroy(&client->watchdog);
154
+ } else {
155
+ esrb_watchdog_abandon(&client->watchdog);
156
+ }
157
+ if (client->wakeup_fd[0] >= 0) {
158
+ close(client->wakeup_fd[0]);
159
+ }
160
+ if (client->wakeup_fd[1] >= 0) {
161
+ close(client->wakeup_fd[1]);
162
+ }
163
+ xfree(client);
164
+ }
165
+
166
+ static size_t
167
+ client_size(const void *pointer)
168
+ {
169
+ esrb_client_t const *client = pointer;
170
+ return client == NULL ? 0
171
+ : sizeof(*client) + client->queue.capacity *
172
+ (sizeof(esrb_slot_t) + sizeof(esrb_watchdog_request_t) + sizeof(esrb_watchdog_entry_t));
173
+ }
174
+
175
+ const rb_data_type_t esrb_client_type = {
176
+ .wrap_struct_name = "EndpointSecurity::Client",
177
+ .function = {.dfree = client_free, .dsize = client_size},
178
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY
179
+ };
180
+
181
+ static VALUE
182
+ client_allocate(VALUE klass)
183
+ {
184
+ esrb_client_t *client;
185
+ VALUE object = TypedData_Make_Struct(klass, esrb_client_t, &esrb_client_type, client);
186
+ client->wakeup_fd[0] = -1;
187
+ client->wakeup_fd[1] = -1;
188
+ client->watchdog_thread_started = false;
189
+ atomic_init(&client->closed, true);
190
+ return object;
191
+ }
192
+
193
+ static esrb_client_t *
194
+ get_client(VALUE self)
195
+ {
196
+ esrb_client_t *client;
197
+ TypedData_Get_Struct(self, esrb_client_t, &esrb_client_type, client);
198
+ if (client->owner_pid != getpid()) {
199
+ rb_raise(rb_path2class("EndpointSecurity::ForkedClientError"), "Endpoint Security clients cannot be used after fork");
200
+ }
201
+ return client;
202
+ }
203
+
204
+ static esrb_client_t *
205
+ get_open_client(VALUE self)
206
+ {
207
+ esrb_client_t *client = get_client(self);
208
+ if (atomic_load_explicit(&client->closed, memory_order_acquire)) {
209
+ rb_raise(rb_path2class("EndpointSecurity::ClientError"), "client is closed");
210
+ }
211
+ return client;
212
+ }
213
+
214
+ static void __attribute__((noreturn))
215
+ raise_new_client_error(es_new_client_result_t result)
216
+ {
217
+ const char *klass;
218
+ const char *name;
219
+ switch (result) {
220
+ case ES_NEW_CLIENT_RESULT_ERR_INVALID_ARGUMENT:
221
+ klass = "EndpointSecurity::InvalidArgumentError";
222
+ name = "err_invalid_argument";
223
+ break;
224
+ case ES_NEW_CLIENT_RESULT_ERR_NOT_ENTITLED:
225
+ klass = "EndpointSecurity::NotEntitledError";
226
+ name = "err_not_entitled";
227
+ break;
228
+ case ES_NEW_CLIENT_RESULT_ERR_NOT_PERMITTED:
229
+ klass = "EndpointSecurity::NotPermittedError";
230
+ name = "err_not_permitted";
231
+ break;
232
+ case ES_NEW_CLIENT_RESULT_ERR_NOT_PRIVILEGED:
233
+ klass = "EndpointSecurity::NotPrivilegedError";
234
+ name = "err_not_privileged";
235
+ break;
236
+ case ES_NEW_CLIENT_RESULT_ERR_TOO_MANY_CLIENTS:
237
+ klass = "EndpointSecurity::TooManyClientsError";
238
+ name = "err_too_many_clients";
239
+ break;
240
+ default:
241
+ klass = "EndpointSecurity::InternalError";
242
+ name = "err_internal";
243
+ break;
244
+ }
245
+ VALUE diagnostics = rb_path2class("EndpointSecurity::Diagnostics");
246
+ VALUE message = rb_funcall(diagnostics, rb_intern("explain"), 1, ID2SYM(rb_intern(name)));
247
+ rb_exc_raise(rb_exc_new_str(rb_path2class(klass), message));
248
+ }
249
+
250
+ void
251
+ esrb_notify(esrb_client_t *client)
252
+ {
253
+ bool expected = false;
254
+ if (!atomic_compare_exchange_strong_explicit(
255
+ &client->notified, &expected, true, memory_order_acq_rel, memory_order_relaxed)) {
256
+ return;
257
+ }
258
+
259
+ unsigned char byte = 1;
260
+ ssize_t ignored = write(client->wakeup_fd[1], &byte, sizeof(byte));
261
+ (void)ignored;
262
+ }
263
+
264
+ static uint64_t
265
+ deadline_fire_time(esrb_client_t *client, uint64_t deadline)
266
+ {
267
+ uint64_t now = mach_absolute_time();
268
+ if (deadline <= now) {
269
+ return now;
270
+ }
271
+ uint64_t remaining = deadline - now;
272
+ uint64_t advance = client->deadline_margin == 1.0
273
+ ? remaining
274
+ : (uint64_t)((double)remaining * client->deadline_margin);
275
+ if (advance < client->min_margin_ticks) {
276
+ advance = client->min_margin_ticks;
277
+ }
278
+ return advance >= remaining ? now : deadline - advance;
279
+ }
280
+
281
+ static void
282
+ record_sequence_gaps(esrb_client_t *client, const es_message_t *message)
283
+ {
284
+ if (message->version >= 4) {
285
+ uint64_t previous = atomic_exchange_explicit(&client->last_global_seq, message->global_seq_num, memory_order_relaxed);
286
+ bool seen = atomic_exchange_explicit(&client->seen_global_seq, true, memory_order_relaxed);
287
+ if (seen && message->global_seq_num > previous + 1) {
288
+ atomic_fetch_add_explicit(&client->seq_gaps, message->global_seq_num - previous - 1, memory_order_relaxed);
289
+ }
290
+ } else if (message->version >= 2 && message->event_type >= 0 && message->event_type < ES_EVENT_TYPE_LAST) {
291
+ size_t index = (size_t)message->event_type;
292
+ uint64_t previous = atomic_exchange_explicit(&client->last_event_seq[index], message->seq_num, memory_order_relaxed);
293
+ bool seen = atomic_exchange_explicit(&client->seen_event_seq[index], true, memory_order_relaxed);
294
+ if (seen && message->seq_num > previous + 1) {
295
+ atomic_fetch_add_explicit(&client->seq_gaps, message->seq_num - previous - 1, memory_order_relaxed);
296
+ }
297
+ }
298
+ }
299
+
300
+ static void
301
+ handle_message(esrb_client_t *client, es_client_t *native_client, const es_message_t *message)
302
+ {
303
+ atomic_fetch_add_explicit(&client->active_callbacks, 1, memory_order_acquire);
304
+ if (atomic_load_explicit(&client->closed, memory_order_acquire)) {
305
+ if (message->action_type == ES_ACTION_TYPE_AUTH) {
306
+ esrb_send_response(client, native_client, message, client->default_auth,
307
+ client->default_auth == ES_AUTH_RESULT_ALLOW ? UINT32_MAX : 0, client->default_cache);
308
+ }
309
+ atomic_fetch_sub_explicit(&client->active_callbacks, 1, memory_order_release);
310
+ return;
311
+ }
312
+ record_sequence_gaps(client, message);
313
+ es_retain_message(message);
314
+ esrb_slot_t *slot = esrb_queue_enqueue(&client->queue);
315
+ if (slot == NULL) {
316
+ if (message->action_type == ES_ACTION_TYPE_AUTH) {
317
+ esrb_send_response(client, native_client, message, client->default_auth,
318
+ client->default_auth == ES_AUTH_RESULT_ALLOW ? UINT32_MAX : 0, client->default_cache);
319
+ }
320
+ atomic_fetch_add_explicit(&client->dropped, 1, memory_order_relaxed);
321
+ es_release_message(message);
322
+ atomic_fetch_sub_explicit(&client->active_callbacks, 1, memory_order_release);
323
+ return;
324
+ }
325
+
326
+ slot->message = message;
327
+ slot->client = native_client;
328
+ slot->fire_at = message->action_type == ES_ACTION_TYPE_AUTH ? deadline_fire_time(client, message->deadline) : UINT64_MAX;
329
+ atomic_store_explicit(&slot->answer_state,
330
+ message->action_type == ES_ACTION_TYPE_AUTH ? ESRB_ANSWER_PENDING : ESRB_ANSWER_NOT_AUTH, memory_order_relaxed);
331
+ atomic_store_explicit(&slot->occupied, true, memory_order_release);
332
+ if (message->action_type == ES_ACTION_TYPE_AUTH && !esrb_watchdog_arm(&client->watchdog, slot)) {
333
+ client_answer_slot(client, slot);
334
+ }
335
+ esrb_queue_publish(&client->queue, slot);
336
+ esrb_notify(client);
337
+ atomic_fetch_sub_explicit(&client->active_callbacks, 1, memory_order_release);
338
+ }
339
+
340
+ static VALUE
341
+ client_initialize(int argc, VALUE *argv, VALUE self)
342
+ {
343
+ VALUE options;
344
+ rb_scan_args(argc, argv, "0:", &options);
345
+ esrb_client_t *client;
346
+ TypedData_Get_Struct(self, esrb_client_t, &esrb_client_type, client);
347
+
348
+ size_t queue_depth = 8192;
349
+ client->default_auth = ES_AUTH_RESULT_ALLOW;
350
+ client->default_cache = false;
351
+ client->strict_cache = false;
352
+ client->strict_version = false;
353
+ client->warn_on_truncated_path = false;
354
+ client->deadline_margin = 0.2;
355
+ uint64_t min_margin_ns = 5000000ULL;
356
+ if (!NIL_P(options)) {
357
+ VALUE depth = rb_hash_aref(options, ID2SYM(rb_intern("queue_depth")));
358
+ VALUE auth = rb_hash_aref(options, ID2SYM(rb_intern("auth_default")));
359
+ VALUE margin = rb_hash_aref(options, ID2SYM(rb_intern("deadline_margin")));
360
+ VALUE minimum = rb_hash_aref(options, ID2SYM(rb_intern("min_margin_ns")));
361
+ VALUE strict_cache = rb_hash_aref(options, ID2SYM(rb_intern("strict_cache")));
362
+ VALUE strict_version = rb_hash_aref(options, ID2SYM(rb_intern("strict_version")));
363
+ VALUE default_cache = rb_hash_aref(options, ID2SYM(rb_intern("default_cache")));
364
+ VALUE on_full = rb_hash_aref(options, ID2SYM(rb_intern("on_full")));
365
+ VALUE warn_on_truncated_path = rb_hash_aref(options, ID2SYM(rb_intern("warn_on_truncated_path")));
366
+ queue_depth = NIL_P(depth) ? queue_depth : NUM2SIZET(depth);
367
+ client->deadline_margin = NIL_P(margin) ? client->deadline_margin : NUM2DBL(margin);
368
+ min_margin_ns = NIL_P(minimum) ? min_margin_ns : NUM2ULL(minimum);
369
+ client->strict_cache = RTEST(strict_cache);
370
+ client->strict_version = RTEST(strict_version);
371
+ client->default_cache = RTEST(default_cache);
372
+ client->warn_on_truncated_path = RTEST(warn_on_truncated_path);
373
+ if (!NIL_P(on_full)) {
374
+ Check_Type(on_full, T_SYMBOL);
375
+ if (SYM2ID(on_full) != rb_intern("drop") && SYM2ID(on_full) != rb_intern("respond_default")) {
376
+ rb_raise(rb_eArgError, "on_full must be :drop or :respond_default");
377
+ }
378
+ }
379
+ if (!NIL_P(auth)) {
380
+ Check_Type(auth, T_SYMBOL);
381
+ if (SYM2ID(auth) == rb_intern("deny")) {
382
+ client->default_auth = ES_AUTH_RESULT_DENY;
383
+ } else if (SYM2ID(auth) != rb_intern("allow")) {
384
+ rb_raise(rb_eArgError, "auth_default must be :allow or :deny");
385
+ }
386
+ }
387
+ }
388
+ if (!isfinite(client->deadline_margin) || client->deadline_margin < 0.0 || client->deadline_margin > 1.0) {
389
+ rb_raise(rb_eArgError, "deadline_margin must be between 0.0 and 1.0");
390
+ }
391
+ if (!esrb_queue_init(&client->queue, queue_depth)) {
392
+ rb_raise(rb_eArgError, "queue_depth must be a power of two and at least 2");
393
+ }
394
+ if (!esrb_watchdog_init(&client->watchdog, queue_depth)) {
395
+ esrb_queue_destroy(&client->queue);
396
+ rb_raise(rb_eNoMemError, "failed to allocate Endpoint Security watchdog");
397
+ }
398
+ if (pipe(client->wakeup_fd) != 0) {
399
+ esrb_watchdog_destroy(&client->watchdog);
400
+ esrb_queue_destroy(&client->queue);
401
+ rb_sys_fail("pipe");
402
+ }
403
+ if (fcntl(client->wakeup_fd[0], F_SETFL, O_NONBLOCK) == -1 ||
404
+ fcntl(client->wakeup_fd[1], F_SETFL, O_NONBLOCK) == -1) {
405
+ int error = errno;
406
+ close(client->wakeup_fd[0]);
407
+ close(client->wakeup_fd[1]);
408
+ client->wakeup_fd[0] = -1;
409
+ client->wakeup_fd[1] = -1;
410
+ esrb_watchdog_destroy(&client->watchdog);
411
+ esrb_queue_destroy(&client->queue);
412
+ errno = error;
413
+ rb_sys_fail("fcntl");
414
+ }
415
+
416
+ mach_timebase_info_data_t info;
417
+ mach_timebase_info(&info);
418
+ client->min_margin_ticks = min_margin_ns > UINT64_MAX / info.denom
419
+ ? UINT64_MAX
420
+ : min_margin_ns * info.denom / info.numer;
421
+ client->owner_pid = getpid();
422
+ atomic_init(&client->notified, false);
423
+ atomic_init(&client->active_callbacks, 0);
424
+ atomic_init(&client->dropped, 0);
425
+ atomic_init(&client->delivered, 0);
426
+ atomic_init(&client->timeouts, 0);
427
+ atomic_init(&client->response_errors, 0);
428
+ atomic_init(&client->delete_errors, 0);
429
+ atomic_init(&client->errors, 0);
430
+ atomic_init(&client->seq_gaps, 0);
431
+ atomic_init(&client->leaked_messages, 0);
432
+ atomic_init(&client->last_global_seq, 0);
433
+ atomic_init(&client->seen_global_seq, false);
434
+ for (size_t index = 0; index < ES_EVENT_TYPE_LAST; index++) {
435
+ atomic_init(&client->last_event_seq[index], 0);
436
+ atomic_init(&client->seen_event_seq[index], false);
437
+ }
438
+ atomic_init(&client->watchdog_running, true);
439
+ atomic_init(&client->watchdog_stopped, false);
440
+ atomic_init(&client->delete_ready, false);
441
+ atomic_init(&client->client_ready, false);
442
+ atomic_init(&client->creation_result, ES_NEW_CLIENT_RESULT_ERR_INTERNAL);
443
+ atomic_store_explicit(&client->closed, false, memory_order_release);
444
+
445
+ if (pthread_create(&client->watchdog_thread, NULL, client_native_main, client) != 0) {
446
+ atomic_store_explicit(&client->watchdog_running, false, memory_order_release);
447
+ close(client->wakeup_fd[0]);
448
+ close(client->wakeup_fd[1]);
449
+ client->wakeup_fd[0] = -1;
450
+ client->wakeup_fd[1] = -1;
451
+ esrb_watchdog_destroy(&client->watchdog);
452
+ esrb_queue_destroy(&client->queue);
453
+ atomic_store_explicit(&client->closed, true, memory_order_release);
454
+ rb_raise(rb_eRuntimeError, "failed to create Endpoint Security watchdog");
455
+ }
456
+ client->watchdog_thread_started = true;
457
+
458
+ pthread_mutex_lock(&client->watchdog.mutex);
459
+ while (!atomic_load_explicit(&client->client_ready, memory_order_acquire)) {
460
+ pthread_cond_wait(&client->watchdog.condition, &client->watchdog.mutex);
461
+ }
462
+ pthread_mutex_unlock(&client->watchdog.mutex);
463
+ es_new_client_result_t result = atomic_load_explicit(&client->creation_result, memory_order_relaxed);
464
+ if (result != ES_NEW_CLIENT_RESULT_SUCCESS) {
465
+ pthread_join(client->watchdog_thread, NULL);
466
+ client->watchdog_thread_started = false;
467
+ close(client->wakeup_fd[0]);
468
+ close(client->wakeup_fd[1]);
469
+ client->wakeup_fd[0] = -1;
470
+ client->wakeup_fd[1] = -1;
471
+ esrb_watchdog_destroy(&client->watchdog);
472
+ esrb_queue_destroy(&client->queue);
473
+ atomic_store_explicit(&client->closed, true, memory_order_release);
474
+ raise_new_client_error(result);
475
+ }
476
+ return self;
477
+ }
478
+
479
+ static VALUE
480
+ client_close(VALUE self)
481
+ {
482
+ client_close_native(get_client(self));
483
+ return Qnil;
484
+ }
485
+
486
+ static VALUE
487
+ client_closed_p(VALUE self)
488
+ {
489
+ return atomic_load_explicit(&get_client(self)->closed, memory_order_acquire) ? Qtrue : Qfalse;
490
+ }
491
+
492
+ static VALUE
493
+ client_wakeup_fd(VALUE self)
494
+ {
495
+ return INT2NUM(get_open_client(self)->wakeup_fd[0]);
496
+ }
497
+
498
+ static VALUE
499
+ client_wake(VALUE self)
500
+ {
501
+ esrb_notify(get_client(self));
502
+ return Qnil;
503
+ }
504
+
505
+ static VALUE
506
+ client_drain(int argc, VALUE *argv, VALUE self)
507
+ {
508
+ VALUE maximum_value;
509
+ rb_scan_args(argc, argv, "01", &maximum_value);
510
+ long maximum = NIL_P(maximum_value) ? 256 : NUM2LONG(maximum_value);
511
+ esrb_client_t *client = get_open_client(self);
512
+ unsigned char buffer[256];
513
+ while (read(client->wakeup_fd[0], buffer, sizeof(buffer)) > 0) {
514
+ }
515
+
516
+ VALUE messages = rb_ary_new_capa(maximum);
517
+ for (long index = 0; index < maximum; index++) {
518
+ esrb_slot_t *slot = esrb_queue_dequeue(&client->queue);
519
+ if (slot == NULL) {
520
+ break;
521
+ }
522
+ rb_ary_push(messages, esrb_message_wrap(self, client, slot));
523
+ atomic_fetch_add_explicit(&client->delivered, 1, memory_order_relaxed);
524
+ }
525
+
526
+ atomic_store_explicit(&client->notified, false, memory_order_release);
527
+ if (esrb_queue_depth(&client->queue) > 0) {
528
+ esrb_notify(client);
529
+ }
530
+ return messages;
531
+ }
532
+
533
+ static VALUE
534
+ client_subscribe(VALUE self, VALUE values)
535
+ {
536
+ esrb_client_t *client = get_open_client(self);
537
+ Check_Type(values, T_ARRAY);
538
+ long count = RARRAY_LEN(values);
539
+ if (count <= 0 || count > UINT32_MAX) {
540
+ rb_raise(rb_eArgError, "at least one event is required");
541
+ }
542
+ VALUE storage = rb_str_new(NULL, count * (long)sizeof(es_event_type_t));
543
+ es_event_type_t *events = (es_event_type_t *)RSTRING_PTR(storage);
544
+ for (long index = 0; index < count; index++) {
545
+ events[index] = (es_event_type_t)NUM2INT(rb_ary_entry(values, index));
546
+ }
547
+ es_return_t result = es_subscribe(client->client, events, (uint32_t)count);
548
+ RB_GC_GUARD(storage);
549
+ if (result != ES_RETURN_SUCCESS) {
550
+ rb_raise(rb_path2class("EndpointSecurity::SubscriptionError"), "es_subscribe failed");
551
+ }
552
+ return Qtrue;
553
+ }
554
+
555
+ static VALUE
556
+ client_unsubscribe(VALUE self, VALUE values)
557
+ {
558
+ esrb_client_t *client = get_open_client(self);
559
+ if (NIL_P(values)) {
560
+ if (es_unsubscribe_all(client->client) != ES_RETURN_SUCCESS) {
561
+ rb_raise(rb_path2class("EndpointSecurity::SubscriptionError"), "es_unsubscribe_all failed");
562
+ }
563
+ return Qtrue;
564
+ }
565
+
566
+ Check_Type(values, T_ARRAY);
567
+ long count = RARRAY_LEN(values);
568
+ if (count > UINT32_MAX) {
569
+ rb_raise(rb_eArgError, "too many events");
570
+ }
571
+ VALUE storage = rb_str_new(NULL, count * (long)sizeof(es_event_type_t));
572
+ es_event_type_t *events = (es_event_type_t *)RSTRING_PTR(storage);
573
+ for (long index = 0; index < count; index++) {
574
+ events[index] = (es_event_type_t)NUM2INT(rb_ary_entry(values, index));
575
+ }
576
+ es_return_t result = es_unsubscribe(client->client, events, (uint32_t)count);
577
+ RB_GC_GUARD(storage);
578
+ if (result != ES_RETURN_SUCCESS) {
579
+ rb_raise(rb_path2class("EndpointSecurity::SubscriptionError"), "es_unsubscribe failed");
580
+ }
581
+ return Qtrue;
582
+ }
583
+
584
+ typedef struct {
585
+ es_event_type_t *values;
586
+ size_t count;
587
+ } esrb_subscriptions_t;
588
+
589
+ static VALUE
590
+ copy_subscriptions(VALUE context_value)
591
+ {
592
+ esrb_subscriptions_t *context = (esrb_subscriptions_t *)(uintptr_t)context_value;
593
+ VALUE values = rb_ary_new_capa((long)context->count);
594
+ VALUE event_type = rb_path2class("EndpointSecurity::EventType");
595
+ for (size_t index = 0; index < context->count; index++) {
596
+ rb_ary_push(values, rb_funcall(event_type, rb_intern("symbol"), 1, INT2NUM(context->values[index])));
597
+ }
598
+ return values;
599
+ }
600
+
601
+ static VALUE
602
+ release_subscriptions(VALUE context_value)
603
+ {
604
+ esrb_subscriptions_t *context = (esrb_subscriptions_t *)(uintptr_t)context_value;
605
+ free(context->values);
606
+ return Qnil;
607
+ }
608
+
609
+ static VALUE
610
+ client_subscriptions(VALUE self)
611
+ {
612
+ esrb_client_t *client = get_open_client(self);
613
+ esrb_subscriptions_t context = {0};
614
+ if (es_subscriptions(client->client, &context.count, &context.values) != ES_RETURN_SUCCESS) {
615
+ rb_raise(rb_path2class("EndpointSecurity::SubscriptionError"), "es_subscriptions failed");
616
+ }
617
+ if (context.count > LONG_MAX) {
618
+ free(context.values);
619
+ rb_raise(rb_eRangeError, "subscription count is too large");
620
+ }
621
+ return rb_ensure(copy_subscriptions, (VALUE)(uintptr_t)&context,
622
+ release_subscriptions, (VALUE)(uintptr_t)&context);
623
+ }
624
+
625
+ static VALUE
626
+ client_stats(VALUE self)
627
+ {
628
+ esrb_client_t *client = get_client(self);
629
+ VALUE stats = rb_hash_new();
630
+ #define SET_STAT(name, value) rb_hash_aset(stats, ID2SYM(rb_intern(name)), ULL2NUM(value))
631
+ SET_STAT("delivered", atomic_load_explicit(&client->delivered, memory_order_relaxed));
632
+ SET_STAT("dropped", atomic_load_explicit(&client->dropped, memory_order_relaxed));
633
+ SET_STAT("timeouts", atomic_load_explicit(&client->timeouts, memory_order_relaxed));
634
+ SET_STAT("response_errors", atomic_load_explicit(&client->response_errors, memory_order_relaxed));
635
+ SET_STAT("delete_errors", atomic_load_explicit(&client->delete_errors, memory_order_relaxed));
636
+ SET_STAT("errors", atomic_load_explicit(&client->errors, memory_order_relaxed));
637
+ SET_STAT("queue_depth_max", atomic_load_explicit(&client->queue.depth_max, memory_order_relaxed));
638
+ SET_STAT("queue_depth", esrb_queue_depth(&client->queue));
639
+ SET_STAT("seq_gaps", atomic_load_explicit(&client->seq_gaps, memory_order_relaxed));
640
+ SET_STAT("leaked_messages", atomic_load_explicit(&client->leaked_messages, memory_order_relaxed));
641
+ #undef SET_STAT
642
+ return stats;
643
+ }
644
+
645
+ bool
646
+ esrb_respond_slot(esrb_client_t *client, esrb_slot_t *slot, es_auth_result_t result, uint32_t flags, bool cache,
647
+ es_respond_result_t *response)
648
+ {
649
+ if (atomic_load_explicit(&client->closed, memory_order_acquire)) {
650
+ return false;
651
+ }
652
+ uint32_t expected = ESRB_ANSWER_PENDING;
653
+ if (!atomic_compare_exchange_strong_explicit(
654
+ &slot->answer_state, &expected, ESRB_ANSWER_ANSWERED, memory_order_acq_rel, memory_order_acquire)) {
655
+ return false;
656
+ }
657
+
658
+ es_respond_result_t result_code = esrb_send_response(client, slot->client, slot->message, result, flags, cache);
659
+ if (response != NULL) {
660
+ *response = result_code;
661
+ }
662
+ return result_code == ES_RESPOND_RESULT_SUCCESS;
663
+ }
664
+
665
+ #ifdef ESRB_MOCK
666
+ static VALUE
667
+ mock_inject(int argc, VALUE *argv, VALUE module)
668
+ {
669
+ (void)module;
670
+ VALUE client_value;
671
+ VALUE options;
672
+ rb_scan_args(argc, argv, "1:", &client_value, &options);
673
+ VALUE event = rb_hash_fetch(options, ID2SYM(rb_intern("event")));
674
+ VALUE auth_value = rb_hash_aref(options, ID2SYM(rb_intern("auth")));
675
+ VALUE action_value = rb_hash_aref(options, ID2SYM(rb_intern("action_type")));
676
+ VALUE deadline_ms = rb_hash_aref(options, ID2SYM(rb_intern("deadline_ms")));
677
+ VALUE version_value = rb_hash_aref(options, ID2SYM(rb_intern("version")));
678
+ VALUE result_type_value = rb_hash_aref(options, ID2SYM(rb_intern("result_type")));
679
+ VALUE result_value = rb_hash_aref(options, ID2SYM(rb_intern("result")));
680
+ VALUE source_es_client = rb_hash_aref(options, ID2SYM(rb_intern("source_es_client")));
681
+ esrb_client_t *client = get_open_client(client_value);
682
+ mach_timebase_info_data_t info;
683
+ mach_timebase_info(&info);
684
+ uint64_t delay = NIL_P(deadline_ms) ? 1000 : NUM2ULL(deadline_ms);
685
+ uint64_t deadline = mach_absolute_time() + delay * 1000000ULL * info.denom / info.numer;
686
+ es_event_type_t event_type = (es_event_type_t)NUM2INT(event);
687
+ size_t event_size = 0;
688
+ for (size_t index = 0; index < esrb_event_schema_count; index++) {
689
+ if (esrb_event_schemas[index].event_type == event_type && esrb_event_schemas[index].indirect) {
690
+ event_size = esrb_event_schemas[index].size;
691
+ break;
692
+ }
693
+ }
694
+ uint32_t version = NIL_P(version_value) ? 4 : NUM2UINT(version_value);
695
+ es_action_type_t action_type = NIL_P(action_value)
696
+ ? (RTEST(auth_value) ? ES_ACTION_TYPE_AUTH : ES_ACTION_TYPE_NOTIFY)
697
+ : (es_action_type_t)NUM2INT(action_value);
698
+ es_result_type_t result_type = NIL_P(result_type_value) ? ES_RESULT_TYPE_AUTH : (es_result_type_t)NUM2INT(result_type_value);
699
+ uint32_t result = NIL_P(result_value) ? ES_AUTH_RESULT_ALLOW : NUM2UINT(result_value);
700
+ esmock_inject(
701
+ client->client, event_type, action_type, deadline, version, event_size, result_type, result, RTEST(source_es_client));
702
+ return Qnil;
703
+ }
704
+
705
+ static VALUE
706
+ mock_response_count(VALUE module)
707
+ {
708
+ (void)module;
709
+ return SIZET2NUM(esmock_response_count());
710
+ }
711
+
712
+ static VALUE
713
+ mock_last_response(VALUE module)
714
+ {
715
+ (void)module;
716
+ return UINT2NUM(esmock_last_response());
717
+ }
718
+
719
+ static VALUE
720
+ mock_client_count(VALUE module)
721
+ {
722
+ (void)module;
723
+ return SIZET2NUM(esmock_client_count());
724
+ }
725
+
726
+ static VALUE
727
+ mock_delete_on_creator_thread(VALUE module)
728
+ {
729
+ (void)module;
730
+ return esmock_delete_on_creator_thread() ? Qtrue : Qfalse;
731
+ }
732
+
733
+ static VALUE
734
+ mock_reset(VALUE module)
735
+ {
736
+ (void)module;
737
+ esmock_reset();
738
+ return Qnil;
739
+ }
740
+
741
+ static VALUE
742
+ mock_set_new_client_result(VALUE module, VALUE result)
743
+ {
744
+ (void)module;
745
+ esmock_set_new_client_result((es_new_client_result_t)NUM2INT(result));
746
+ return result;
747
+ }
748
+
749
+ static VALUE
750
+ mock_set_respond_result(VALUE module, VALUE result)
751
+ {
752
+ (void)module;
753
+ esmock_set_respond_result((es_respond_result_t)NUM2INT(result));
754
+ return result;
755
+ }
756
+
757
+ static VALUE
758
+ mock_set_delete_result(VALUE module, VALUE result)
759
+ {
760
+ (void)module;
761
+ esmock_set_delete_result((es_return_t)NUM2INT(result));
762
+ return result;
763
+ }
764
+ #endif
765
+
766
+ void
767
+ esrb_init_client(VALUE endpoint_security)
768
+ {
769
+ c_client = rb_define_class_under(endpoint_security, "Client", rb_cObject);
770
+ rb_define_alloc_func(c_client, client_allocate);
771
+ rb_define_method(c_client, "initialize", client_initialize, -1);
772
+ rb_define_method(c_client, "close", client_close, 0);
773
+ rb_define_method(c_client, "closed?", client_closed_p, 0);
774
+ rb_define_method(c_client, "__wakeup_fd", client_wakeup_fd, 0);
775
+ rb_define_method(c_client, "__wake", client_wake, 0);
776
+ rb_define_method(c_client, "__drain", client_drain, -1);
777
+ rb_define_method(c_client, "__subscribe", client_subscribe, 1);
778
+ rb_define_method(c_client, "__unsubscribe", client_unsubscribe, 1);
779
+ rb_define_method(c_client, "__subscriptions", client_subscriptions, 0);
780
+ rb_define_method(c_client, "stats", client_stats, 0);
781
+ esrb_init_mute(c_client);
782
+
783
+ #ifdef ESRB_MOCK
784
+ VALUE mock = rb_define_module_under(endpoint_security, "Mock");
785
+ rb_define_singleton_method(mock, "inject", mock_inject, -1);
786
+ rb_define_singleton_method(mock, "response_count", mock_response_count, 0);
787
+ rb_define_singleton_method(mock, "last_response", mock_last_response, 0);
788
+ rb_define_singleton_method(mock, "client_count", mock_client_count, 0);
789
+ rb_define_singleton_method(mock, "delete_on_creator_thread?", mock_delete_on_creator_thread, 0);
790
+ rb_define_singleton_method(mock, "new_client_result=", mock_set_new_client_result, 1);
791
+ rb_define_singleton_method(mock, "respond_result=", mock_set_respond_result, 1);
792
+ rb_define_singleton_method(mock, "delete_result=", mock_set_delete_result, 1);
793
+ rb_define_singleton_method(mock, "reset", mock_reset, 0);
794
+ #endif
795
+ }