datadog 2.9.0 → 2.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +27 -1
  3. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +2 -2
  4. data/ext/datadog_profiling_native_extension/collectors_stack.h +2 -2
  5. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +2 -5
  6. data/ext/datadog_profiling_native_extension/heap_recorder.c +50 -92
  7. data/ext/datadog_profiling_native_extension/heap_recorder.h +1 -1
  8. data/ext/datadog_profiling_native_extension/stack_recorder.c +9 -22
  9. data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -1
  10. data/lib/datadog/appsec/actions_handler.rb +27 -0
  11. data/lib/datadog/appsec/component.rb +14 -8
  12. data/lib/datadog/appsec/configuration/settings.rb +9 -0
  13. data/lib/datadog/appsec/context.rb +28 -8
  14. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +6 -2
  15. data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +1 -7
  16. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +4 -5
  17. data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +1 -1
  18. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +15 -12
  19. data/lib/datadog/appsec/contrib/rack/reactive/request.rb +1 -1
  20. data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +1 -1
  21. data/lib/datadog/appsec/contrib/rack/reactive/response.rb +1 -1
  22. data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +3 -3
  23. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +11 -22
  24. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +5 -4
  25. data/lib/datadog/appsec/contrib/rails/patcher.rb +3 -13
  26. data/lib/datadog/appsec/contrib/rails/reactive/action.rb +1 -1
  27. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +10 -8
  28. data/lib/datadog/appsec/contrib/sinatra/patcher.rb +3 -26
  29. data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +1 -1
  30. data/lib/datadog/appsec/ext.rb +6 -1
  31. data/lib/datadog/appsec/metrics/collector.rb +38 -0
  32. data/lib/datadog/appsec/metrics/exporter.rb +35 -0
  33. data/lib/datadog/appsec/metrics/telemetry.rb +23 -0
  34. data/lib/datadog/appsec/metrics.rb +13 -0
  35. data/lib/datadog/appsec/monitor/gateway/watcher.rb +5 -4
  36. data/lib/datadog/appsec/monitor/reactive/set_user.rb +1 -1
  37. data/lib/datadog/appsec/processor.rb +4 -3
  38. data/lib/datadog/appsec/response.rb +18 -80
  39. data/lib/datadog/appsec/security_engine/result.rb +67 -0
  40. data/lib/datadog/appsec/security_engine/runner.rb +88 -0
  41. data/lib/datadog/appsec/security_engine.rb +9 -0
  42. data/lib/datadog/appsec.rb +14 -5
  43. data/lib/datadog/di/component.rb +2 -0
  44. data/lib/datadog/di/probe_notification_builder.rb +6 -0
  45. data/lib/datadog/di/redactor.rb +0 -1
  46. data/lib/datadog/di/remote.rb +26 -5
  47. data/lib/datadog/tracing/contrib/aws/integration.rb +1 -1
  48. data/lib/datadog/tracing/contrib/extensions.rb +15 -3
  49. data/lib/datadog/tracing/contrib/http/integration.rb +3 -0
  50. data/lib/datadog/version.rb +1 -1
  51. metadata +32 -18
  52. data/lib/datadog/appsec/contrib/sinatra/ext.rb +0 -14
  53. data/lib/datadog/appsec/processor/context.rb +0 -107
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5666f79db7cd7abdb43c961e51978ef2711ec4566ba8090ebafeb074d06dec33
4
- data.tar.gz: 907d0787aedf67f9db8ff57a2f16b6216cbc5514bb2a47005026f0e5263e7168
3
+ metadata.gz: 88fca1176a1b0fe35703ec00277925dd64a377c6736c52674f5aeb3195ec4d86
4
+ data.tar.gz: 92631bc4c25b6132821d30858e99e8cd8308445f99a258226508ed04f4a696ba
5
5
  SHA512:
6
- metadata.gz: 22a55675bb273845ec829245812e9f447767878d924083d505ddc8ec6dc97c28770252b3bbb25f0811ab4bf92e44bf2e7439815b61bc14c8d98a8025c7f1db13
7
- data.tar.gz: 6a71cfdfbb3bd78949c23e11cbfcc9cea6a582925812862e0ff9a8c8f6ecded1807ee0d52e36c4422cb49181e0138e5e5d566a9895ccf46c0a0cd91fc74cfd64
6
+ metadata.gz: 19a5fd9e66f759a1db3aee5cb1496abaa7393381809eaa956d27b8eb045fb993d7f77ef11c305a97b44bd627294e44fde236836d10c9458fc1543846d58e36e1
7
+ data.tar.gz: 42bbfab98df4de69947602daa14f75bbb2cdf170bf8203164fb93f496c16d824aea2f659d93688c67e4f0baf7d5221ea1908a4ab5f714d903a9268916a5387ce
data/CHANGELOG.md CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [2.10.0] - 2025-02-04
6
+
7
+ ### Added
8
+
9
+ * AppSec: Add configuration option(`Datadog.configuration.appsec.rasp_enabled`) to enable/disable Runtime Application Self-Protection checks ([#4311][])
10
+ * AppSec: Add stack trace when SQL Injection attack is detected ([#4321][])
11
+
12
+ ### Changed
13
+
14
+ * Add `logger` gem as dependency ([#4257][])
15
+ * Bump minimum version of `datadog-ruby_core_source` to 3.4 ([#4323][])
16
+
17
+ ### Fixed
18
+
19
+ * Dynamic instrumentation: Fix report probe status when dynamic instrumentation probes fail to instrument ([#4301][])
20
+ * Dynamic instrumentation: Include variables named `env` in probe snapshots ([#4292][])
21
+ * Fix a concurrency issue during application boot ([#4303][])
22
+
5
23
  ## [2.9.0] - 2025-01-15
6
24
 
7
25
  ### Added
@@ -3079,7 +3097,8 @@ Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.3.1
3079
3097
  Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
3080
3098
 
3081
3099
 
3082
- [Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.9.0...master
3100
+ [Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.10.0...master
3101
+ [2.10.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.9.0...v2.10.0
3083
3102
  [2.9.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.8.0...v2.9.0
3084
3103
  [2.8.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.7.1...v2.8.0
3085
3104
  [2.7.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.6.0...v2.7.0
@@ -4550,10 +4569,17 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
4550
4569
  [#4239]: https://github.com/DataDog/dd-trace-rb/issues/4239
4551
4570
  [#4240]: https://github.com/DataDog/dd-trace-rb/issues/4240
4552
4571
  [#4249]: https://github.com/DataDog/dd-trace-rb/issues/4249
4572
+ [#4257]: https://github.com/DataDog/dd-trace-rb/issues/4257
4553
4573
  [#4266]: https://github.com/DataDog/dd-trace-rb/issues/4266
4554
4574
  [#4272]: https://github.com/DataDog/dd-trace-rb/issues/4272
4555
4575
  [#4285]: https://github.com/DataDog/dd-trace-rb/issues/4285
4556
4576
  [#4288]: https://github.com/DataDog/dd-trace-rb/issues/4288
4577
+ [#4292]: https://github.com/DataDog/dd-trace-rb/issues/4292
4578
+ [#4301]: https://github.com/DataDog/dd-trace-rb/issues/4301
4579
+ [#4303]: https://github.com/DataDog/dd-trace-rb/issues/4303
4580
+ [#4311]: https://github.com/DataDog/dd-trace-rb/issues/4311
4581
+ [#4321]: https://github.com/DataDog/dd-trace-rb/issues/4321
4582
+ [#4323]: https://github.com/DataDog/dd-trace-rb/issues/4323
4557
4583
  [@AdrianLC]: https://github.com/AdrianLC
4558
4584
  [@Azure7111]: https://github.com/Azure7111
4559
4585
  [@BabyGroot]: https://github.com/BabyGroot
@@ -17,7 +17,7 @@
17
17
  #include "setup_signal_handler.h"
18
18
  #include "time_helpers.h"
19
19
 
20
- // Used to trigger the execution of Collectors::ThreadState, which implements all of the sampling logic
20
+ // Used to trigger the execution of Collectors::ThreadContext, which implements all of the sampling logic
21
21
  // itself; this class only implements the "when to do it" part.
22
22
  //
23
23
  // This file implements the native bits of the Datadog::Profiling::Collectors::CpuAndWallTimeWorker class
@@ -33,7 +33,7 @@
33
33
  // Currently, sampling Ruby threads requires calling Ruby VM APIs that are only safe to call while holding on to the
34
34
  // global VM lock (and are not async-signal safe -- cannot be called from a signal handler).
35
35
  //
36
- // @ivoanjo: As a note, I don't think we should think of this constraint as set in stone. Since can reach into the Ruby
36
+ // @ivoanjo: As a note, I don't think we should think of this constraint as set in stone. Since we can reach inside the Ruby
37
37
  // internals, we may be able to figure out a way of overcoming it. But it's definitely going to be hard so for now
38
38
  // we're considering it as a given.
39
39
  //
@@ -4,8 +4,8 @@
4
4
 
5
5
  #include "stack_recorder.h"
6
6
 
7
- #define MAX_FRAMES_LIMIT 10000
8
- #define MAX_FRAMES_LIMIT_AS_STRING "10000"
7
+ #define MAX_FRAMES_LIMIT 3000
8
+ #define MAX_FRAMES_LIMIT_AS_STRING "3000"
9
9
 
10
10
  typedef struct sampling_buffer sampling_buffer;
11
11
 
@@ -1450,11 +1450,8 @@ void thread_context_collector_sample_allocation(VALUE self_instance, unsigned in
1450
1450
 
1451
1451
  // Since this is stack allocated, be careful about moving it
1452
1452
  ddog_CharSlice class_name;
1453
- ddog_CharSlice *optional_class_name = NULL;
1454
1453
  char imemo_type[100];
1455
1454
 
1456
- optional_class_name = &class_name;
1457
-
1458
1455
  if (
1459
1456
  type == RUBY_T_OBJECT ||
1460
1457
  type == RUBY_T_CLASS ||
@@ -1510,7 +1507,7 @@ void thread_context_collector_sample_allocation(VALUE self_instance, unsigned in
1510
1507
  class_name = ruby_vm_type; // For other weird internal things we just use the VM type
1511
1508
  }
1512
1509
 
1513
- track_object(state->recorder_instance, new_object, sample_weight, optional_class_name);
1510
+ track_object(state->recorder_instance, new_object, sample_weight, class_name);
1514
1511
 
1515
1512
  per_thread_context *thread_context = get_or_create_context_for(current_thread, state);
1516
1513
 
@@ -1523,7 +1520,7 @@ void thread_context_collector_sample_allocation(VALUE self_instance, unsigned in
1523
1520
  (sample_values) {.alloc_samples = sample_weight, .alloc_samples_unscaled = 1, .heap_sample = true},
1524
1521
  INVALID_TIME, // For now we're not collecting timestamps for allocation events, as per profiling team internal discussions
1525
1522
  &ruby_vm_type,
1526
- optional_class_name,
1523
+ &class_name,
1527
1524
  /* is_gvl_waiting_state: */ false,
1528
1525
  /* is_safe_to_allocate_objects: */ false // Not safe to allocate further inside the NEWOBJ tracepoint
1529
1526
  );
@@ -1,8 +1,6 @@
1
1
  #include "heap_recorder.h"
2
- #include <pthread.h>
3
2
  #include "ruby/st.h"
4
3
  #include "ruby_helpers.h"
5
- #include <errno.h>
6
4
  #include "collectors_stack.h"
7
5
  #include "libdatadog_helpers.h"
8
6
  #include "time_helpers.h"
@@ -30,7 +28,6 @@ typedef struct {
30
28
  char *filename;
31
29
  int32_t line;
32
30
  } heap_frame;
33
- static st_index_t heap_frame_hash(heap_frame*, st_index_t seed);
34
31
 
35
32
  // A compact representation of a stacktrace for a heap allocation.
36
33
  //
@@ -113,14 +110,6 @@ static void object_record_free(object_record*);
113
110
  static VALUE object_record_inspect(object_record*);
114
111
  static object_record SKIPPED_RECORD = {0};
115
112
 
116
- // A wrapper around an object record that is in the process of being recorded and was not
117
- // yet committed.
118
- typedef struct {
119
- // Pointer to the (potentially partial) object_record containing metadata about an ongoing recording.
120
- // When NULL, this symbolizes an unstarted/invalid recording.
121
- object_record *object_record;
122
- } recording;
123
-
124
113
  struct heap_recorder {
125
114
  // Config
126
115
  // Whether the recorder should try to determine approximate sizes for tracked objects.
@@ -140,6 +129,9 @@ struct heap_recorder {
140
129
  // outside the GVL.
141
130
  // NOTE: This table has ownership of its object_records. The keys are longs and so are
142
131
  // passed as values.
132
+ //
133
+ // TODO: @ivoanjo We've evolved to actually never need to look up on object_records (we only insert and iterate),
134
+ // so right now this seems to be just a really really fancy self-resizing list/set.
143
135
  st_table *object_records;
144
136
 
145
137
  // Map[obj_id: long, record: object_record*]
@@ -162,7 +154,7 @@ struct heap_recorder {
162
154
  long last_update_ns;
163
155
 
164
156
  // Data for a heap recording that was started but not yet ended
165
- recording active_recording;
157
+ object_record *active_recording;
166
158
 
167
159
  // Reusable location array, implementing a flyweight pattern for things like iteration.
168
160
  ddog_prof_Location *reusable_locations;
@@ -207,7 +199,7 @@ static int st_object_record_update(st_data_t, st_data_t, st_data_t);
207
199
  static int st_object_records_iterate(st_data_t, st_data_t, st_data_t);
208
200
  static int st_object_records_debug(st_data_t key, st_data_t value, st_data_t extra);
209
201
  static int update_object_record_entry(st_data_t*, st_data_t*, st_data_t, int);
210
- static void commit_recording(heap_recorder*, heap_record*, recording);
202
+ static void commit_recording(heap_recorder *, heap_record *, object_record *active_recording);
211
203
  static VALUE end_heap_allocation_recording(VALUE end_heap_allocation_args);
212
204
  static void heap_recorder_update(heap_recorder *heap_recorder, bool full_update);
213
205
  static inline double ewma_stat(double previous, double current);
@@ -228,7 +220,7 @@ heap_recorder* heap_recorder_new(void) {
228
220
  recorder->object_records = st_init_numtable();
229
221
  recorder->object_records_snapshot = NULL;
230
222
  recorder->reusable_locations = ruby_xcalloc(MAX_FRAMES_LIMIT, sizeof(ddog_prof_Location));
231
- recorder->active_recording = (recording) {0};
223
+ recorder->active_recording = NULL;
232
224
  recorder->size_enabled = true;
233
225
  recorder->sample_rate = 1; // By default do no sampling on top of what allocation profiling already does
234
226
 
@@ -254,9 +246,9 @@ void heap_recorder_free(heap_recorder *heap_recorder) {
254
246
  st_foreach(heap_recorder->heap_records, st_heap_record_entry_free, 0);
255
247
  st_free_table(heap_recorder->heap_records);
256
248
 
257
- if (heap_recorder->active_recording.object_record != NULL && heap_recorder->active_recording.object_record != &SKIPPED_RECORD) {
249
+ if (heap_recorder->active_recording != NULL && heap_recorder->active_recording != &SKIPPED_RECORD) {
258
250
  // If there's a partial object record, clean it up as well
259
- object_record_free(heap_recorder->active_recording.object_record);
251
+ object_record_free(heap_recorder->active_recording);
260
252
  }
261
253
 
262
254
  ruby_xfree(heap_recorder->reusable_locations);
@@ -301,7 +293,7 @@ void heap_recorder_after_fork(heap_recorder *heap_recorder) {
301
293
  //
302
294
  // There is one small caveat though: fork only preserves one thread and in a Ruby app, that
303
295
  // will be the thread holding on to the GVL. Since we support iteration on the heap recorder
304
- // outside of the GVL, any state specific to that interaction may be incosistent after fork
296
+ // outside of the GVL, any state specific to that interaction may be inconsistent after fork
305
297
  // (e.g. an acquired lock for thread safety). Iteration operates on object_records_snapshot
306
298
  // though and that one will be updated on next heap_recorder_prepare_iteration so we really
307
299
  // only need to finish any iteration that might have been left unfinished.
@@ -313,18 +305,17 @@ void heap_recorder_after_fork(heap_recorder *heap_recorder) {
313
305
  heap_recorder->stats_lifetime = (struct stats_lifetime) {0};
314
306
  }
315
307
 
316
- void start_heap_allocation_recording(heap_recorder *heap_recorder, VALUE new_obj, unsigned int weight, ddog_CharSlice *alloc_class) {
308
+ void start_heap_allocation_recording(heap_recorder *heap_recorder, VALUE new_obj, unsigned int weight, ddog_CharSlice alloc_class) {
317
309
  if (heap_recorder == NULL) {
318
310
  return;
319
311
  }
320
312
 
321
- if (heap_recorder->active_recording.object_record != NULL) {
313
+ if (heap_recorder->active_recording != NULL) {
322
314
  rb_raise(rb_eRuntimeError, "Detected consecutive heap allocation recording starts without end.");
323
315
  }
324
316
 
325
- if (heap_recorder->num_recordings_skipped + 1 < heap_recorder->sample_rate) {
326
- heap_recorder->active_recording.object_record = &SKIPPED_RECORD;
327
- heap_recorder->num_recordings_skipped++;
317
+ if (++heap_recorder->num_recordings_skipped < heap_recorder->sample_rate) {
318
+ heap_recorder->active_recording = &SKIPPED_RECORD;
328
319
  return;
329
320
  }
330
321
 
@@ -335,13 +326,15 @@ void start_heap_allocation_recording(heap_recorder *heap_recorder, VALUE new_obj
335
326
  rb_raise(rb_eRuntimeError, "Detected a bignum object id. These are not supported by heap profiling.");
336
327
  }
337
328
 
338
- heap_recorder->active_recording = (recording) {
339
- .object_record = object_record_new(FIX2LONG(ruby_obj_id), NULL, (live_object_data) {
340
- .weight = weight * heap_recorder->sample_rate,
341
- .class = alloc_class != NULL ? string_from_char_slice(*alloc_class) : NULL,
342
- .alloc_gen = rb_gc_count(),
343
- }),
344
- };
329
+ heap_recorder->active_recording = object_record_new(
330
+ FIX2LONG(ruby_obj_id),
331
+ NULL,
332
+ (live_object_data) {
333
+ .weight = weight * heap_recorder->sample_rate,
334
+ .class = string_from_char_slice(alloc_class),
335
+ .alloc_gen = rb_gc_count(),
336
+ }
337
+ );
345
338
  }
346
339
 
347
340
  // end_heap_allocation_recording_with_rb_protect gets called while the stack_recorder is holding one of the profile
@@ -349,6 +342,10 @@ void start_heap_allocation_recording(heap_recorder *heap_recorder, VALUE new_obj
349
342
  // with an rb_protect.
350
343
  __attribute__((warn_unused_result))
351
344
  int end_heap_allocation_recording_with_rb_protect(struct heap_recorder *heap_recorder, ddog_prof_Slice_Location locations) {
345
+ if (heap_recorder == NULL) {
346
+ return 0;
347
+ }
348
+
352
349
  int exception_state;
353
350
  struct end_heap_allocation_args end_heap_allocation_args = {
354
351
  .heap_recorder = heap_recorder,
@@ -364,22 +361,18 @@ static VALUE end_heap_allocation_recording(VALUE end_heap_allocation_args) {
364
361
  struct heap_recorder *heap_recorder = args->heap_recorder;
365
362
  ddog_prof_Slice_Location locations = args->locations;
366
363
 
367
- if (heap_recorder == NULL) {
368
- return Qnil;
369
- }
364
+ object_record *active_recording = heap_recorder->active_recording;
370
365
 
371
- recording active_recording = heap_recorder->active_recording;
372
-
373
- if (active_recording.object_record == NULL) {
366
+ if (active_recording == NULL) {
374
367
  // Recording ended without having been started?
375
368
  rb_raise(rb_eRuntimeError, "Ended a heap recording that was not started");
376
369
  }
377
370
  // From now on, mark the global active recording as invalid so we can short-circuit at any point
378
371
  // and not end up with a still active recording. the local active_recording still holds the
379
372
  // data required for committing though.
380
- heap_recorder->active_recording = (recording) {0};
373
+ heap_recorder->active_recording = NULL;
381
374
 
382
- if (active_recording.object_record == &SKIPPED_RECORD) { // special marker when we decided to skip due to sampling
375
+ if (active_recording == &SKIPPED_RECORD) { // special marker when we decided to skip due to sampling
383
376
  return Qnil;
384
377
  }
385
378
 
@@ -698,6 +691,7 @@ static int st_object_records_iterate(DDTRACE_UNUSED st_data_t key, st_data_t val
698
691
  iteration_data.object_data = record->object_data;
699
692
  iteration_data.locations = (ddog_prof_Slice_Location) {.ptr = locations, .len = stack->frames_len};
700
693
 
694
+ // This is expected to be StackRecorder's add_heap_sample_to_active_profile_without_gvl
701
695
  if (!context->for_each_callback(iteration_data, context->for_each_callback_extra_arg)) {
702
696
  return ST_STOP;
703
697
  }
@@ -715,49 +709,35 @@ static int st_object_records_debug(DDTRACE_UNUSED st_data_t key, st_data_t value
715
709
  return ST_CONTINUE;
716
710
  }
717
711
 
718
- // Struct holding data required for an update operation on heap_records
719
- typedef struct {
720
- // [in] The recording containing the new object record we want to add.
721
- // NOTE: Transfer of ownership of the contained object record is assumed, do not re-use it after call to ::update_object_record_entry
722
- recording recording;
723
-
724
- // [in] The heap recorder where the update is happening.
725
- heap_recorder *heap_recorder;
726
- } object_record_update_data;
727
-
728
- static int update_object_record_entry(DDTRACE_UNUSED st_data_t *key, st_data_t *value, st_data_t data, int existing) {
729
- object_record_update_data *update_data = (object_record_update_data*) data;
730
- recording recording = update_data->recording;
731
- object_record *new_object_record = recording.object_record;
732
- if (existing) {
733
- object_record *existing_record = (object_record*) (*value);
734
-
735
- // This is not supposed to happen, raising...
736
- VALUE existing_inspect = object_record_inspect(existing_record);
737
- VALUE new_inspect = object_record_inspect(new_object_record);
738
- rb_raise(rb_eRuntimeError, "Object ids are supposed to be unique. We got 2 allocation recordings with "
739
- "the same id. previous=%"PRIsVALUE" new=%"PRIsVALUE, existing_inspect, new_inspect);
712
+ static int update_object_record_entry(DDTRACE_UNUSED st_data_t *key, st_data_t *value, st_data_t new_object_record, int existing) {
713
+ if (!existing) {
714
+ (*value) = (st_data_t) new_object_record; // Expected to be a `object_record *`
715
+ } else {
716
+ // If key already existed, we don't touch the existing value, so it can be used for diagnostics
740
717
  }
741
- // Always carry on with the update, we want the new record to be there at the end
742
- (*value) = (st_data_t) new_object_record;
743
718
  return ST_CONTINUE;
744
719
  }
745
720
 
746
- static void commit_recording(heap_recorder *heap_recorder, heap_record *heap_record, recording recording) {
721
+ static void commit_recording(heap_recorder *heap_recorder, heap_record *heap_record, object_record *active_recording) {
747
722
  // Link the object record with the corresponding heap record. This was the last remaining thing we
748
723
  // needed to fully build the object_record.
749
- recording.object_record->heap_record = heap_record;
724
+ active_recording->heap_record = heap_record;
750
725
  if (heap_record->num_tracked_objects == UINT32_MAX) {
751
726
  rb_raise(rb_eRuntimeError, "Reached maximum number of tracked objects for heap record");
752
727
  }
753
728
  heap_record->num_tracked_objects++;
754
729
 
755
- // Update object_records with the data for this new recording
756
- object_record_update_data update_data = (object_record_update_data) {
757
- .heap_recorder = heap_recorder,
758
- .recording = recording,
759
- };
760
- st_update(heap_recorder->object_records, recording.object_record->obj_id, update_object_record_entry, (st_data_t) &update_data);
730
+ int existing_error = st_update(heap_recorder->object_records, active_recording->obj_id, update_object_record_entry, (st_data_t) active_recording);
731
+ if (existing_error) {
732
+ object_record *existing_record = NULL;
733
+ st_lookup(heap_recorder->object_records, active_recording->obj_id, (st_data_t *) &existing_record);
734
+ if (existing_record == NULL) rb_raise(rb_eRuntimeError, "Unexpected NULL when reading existing record");
735
+
736
+ VALUE existing_inspect = object_record_inspect(existing_record);
737
+ VALUE new_inspect = object_record_inspect(active_recording);
738
+ rb_raise(rb_eRuntimeError, "Object ids are supposed to be unique. We got 2 allocation recordings with "
739
+ "the same id. previous={%"PRIsVALUE"} new={%"PRIsVALUE"}", existing_inspect, new_inspect);
740
+ }
761
741
  }
762
742
 
763
743
  // Struct holding data required for an update operation on heap_records
@@ -867,7 +847,6 @@ void heap_record_free(heap_record *record) {
867
847
  ruby_xfree(record);
868
848
  }
869
849
 
870
-
871
850
  // =================
872
851
  // Object Record API
873
852
  // =================
@@ -917,25 +896,6 @@ VALUE object_record_inspect(object_record *record) {
917
896
  // ==============
918
897
  // Heap Frame API
919
898
  // ==============
920
- int heap_frame_cmp(heap_frame *f1, heap_frame *f2) {
921
- int line_diff = (int) (f1->line - f2->line);
922
- if (line_diff != 0) {
923
- return line_diff;
924
- }
925
- int cmp = strcmp(f1->name, f2->name);
926
- if (cmp != 0) {
927
- return cmp;
928
- }
929
- return strcmp(f1->filename, f2->filename);
930
- }
931
-
932
- // TODO: Research potential performance improvements around hashing stuff here
933
- // once we have a benchmarking suite.
934
- // Example: Each call to st_hash is calling murmur_finish and we may want
935
- // to only finish once per structure, not per field?
936
- // Example: There may be a more efficient hashing for line that is not the
937
- // generic st_hash algorithm?
938
-
939
899
  // WARN: Must be kept in-sync with ::char_slice_hash
940
900
  st_index_t string_hash(char *str, st_index_t seed) {
941
901
  return st_hash(str, strlen(str), seed);
@@ -971,9 +931,7 @@ st_index_t ddog_location_hash(ddog_prof_Location location, st_index_t seed) {
971
931
  heap_stack* heap_stack_new(ddog_prof_Slice_Location locations) {
972
932
  uint16_t frames_len = locations.len;
973
933
  if (frames_len > MAX_FRAMES_LIMIT) {
974
- // This should not be happening anyway since MAX_FRAMES_LIMIT should be shared with
975
- // the stacktrace construction mechanism. If it happens, lets just raise. This should
976
- // be safe since only allocate with the GVL anyway.
934
+ // This is not expected as MAX_FRAMES_LIMIT is shared with the stacktrace construction mechanism
977
935
  rb_raise(rb_eRuntimeError, "Found stack with more than %d frames (%d)", MAX_FRAMES_LIMIT, frames_len);
978
936
  }
979
937
  heap_stack *stack = ruby_xcalloc(1, sizeof(heap_stack) + frames_len * sizeof(heap_frame));
@@ -105,7 +105,7 @@ void heap_recorder_after_fork(heap_recorder *heap_recorder);
105
105
  // The sampling weight of this object.
106
106
  //
107
107
  // WARN: It needs to be paired with a ::end_heap_allocation_recording call.
108
- void start_heap_allocation_recording(heap_recorder *heap_recorder, VALUE new_obj, unsigned int weight, ddog_CharSlice *alloc_class);
108
+ void start_heap_allocation_recording(heap_recorder *heap_recorder, VALUE new_obj, unsigned int weight, ddog_CharSlice alloc_class);
109
109
 
110
110
  // End a previously started heap allocation recording on the heap recorder.
111
111
  //
@@ -332,23 +332,16 @@ static VALUE _native_new(VALUE klass) {
332
332
  .serialization_time_ns_min = INT64_MAX,
333
333
  };
334
334
 
335
- // Note: At this point, slot_one_profile and slot_two_profile contain null pointers. Libdatadog validates pointers
335
+ // Note: At this point, slot_one_profile/slot_two_profile contain null pointers. Libdatadog validates pointers
336
336
  // before using them so it's ok for us to go ahead and create the StackRecorder object.
337
337
 
338
- // Note: As of this writing, no new Ruby objects get created and stored in the state. If that ever changes, remember
339
- // to keep them on the stack and mark them with RB_GC_GUARD -- otherwise it's possible for a GC to run and
340
- // since the instance representing the state does not yet exist, such objects will not get marked.
341
-
342
338
  VALUE stack_recorder = TypedData_Wrap_Struct(klass, &stack_recorder_typed_data, state);
343
339
 
344
- // NOTE: We initialize this because we want a new recorder to be operational even without initialization and our
340
+ // NOTE: We initialize this because we want a new recorder to be operational even before #initialize runs and our
345
341
  // default is everything enabled. However, if during recording initialization it turns out we don't want
346
- // heap samples, we will free and reset heap_recorder to NULL, effectively disabling all behaviour specific
347
- // to heap profiling (all calls to heap_recorder_* with a NULL heap recorder are noops).
342
+ // heap samples, we will free and reset heap_recorder back to NULL.
348
343
  state->heap_recorder = heap_recorder_new();
349
344
 
350
- // Note: Don't raise exceptions after this point, since it'll lead to libdatadog memory leaking!
351
-
352
345
  initialize_profiles(state, sample_types);
353
346
 
354
347
  return stack_recorder;
@@ -372,22 +365,17 @@ static void initialize_profiles(stack_recorder_state *state, ddog_prof_Slice_Val
372
365
  rb_raise(rb_eRuntimeError, "Failed to initialize slot one profile: %"PRIsVALUE, get_error_details_and_drop(&slot_one_profile_result.err));
373
366
  }
374
367
 
368
+ state->profile_slot_one = (profile_slot) { .profile = slot_one_profile_result.ok };
369
+
375
370
  ddog_prof_Profile_NewResult slot_two_profile_result =
376
371
  ddog_prof_Profile_new(sample_types, NULL /* period is optional */, NULL /* start_time is optional */);
377
372
 
378
373
  if (slot_two_profile_result.tag == DDOG_PROF_PROFILE_NEW_RESULT_ERR) {
379
- // Uff! Though spot. We need to make sure to properly clean up the other profile as well first
380
- ddog_prof_Profile_drop(&slot_one_profile_result.ok);
381
- // And now we can raise...
374
+ // Note: No need to take any special care of slot one, it'll get cleaned up by stack_recorder_typed_data_free
382
375
  rb_raise(rb_eRuntimeError, "Failed to initialize slot two profile: %"PRIsVALUE, get_error_details_and_drop(&slot_two_profile_result.err));
383
376
  }
384
377
 
385
- state->profile_slot_one = (profile_slot) {
386
- .profile = slot_one_profile_result.ok,
387
- };
388
- state->profile_slot_two = (profile_slot) {
389
- .profile = slot_two_profile_result.ok,
390
- };
378
+ state->profile_slot_two = (profile_slot) { .profile = slot_two_profile_result.ok };
391
379
  }
392
380
 
393
381
  static void stack_recorder_typed_data_free(void *state_ptr) {
@@ -651,7 +639,7 @@ void record_sample(VALUE recorder_instance, ddog_prof_Slice_Location locations,
651
639
  }
652
640
  }
653
641
 
654
- void track_object(VALUE recorder_instance, VALUE new_object, unsigned int sample_weight, ddog_CharSlice *alloc_class) {
642
+ void track_object(VALUE recorder_instance, VALUE new_object, unsigned int sample_weight, ddog_CharSlice alloc_class) {
655
643
  stack_recorder_state *state;
656
644
  TypedData_Get_Struct(recorder_instance, stack_recorder_state, &stack_recorder_typed_data, state);
657
645
  // FIXME: Heap sampling currently has to be done in 2 parts because the construction of locations is happening
@@ -926,8 +914,7 @@ static VALUE _native_record_endpoint(DDTRACE_UNUSED VALUE _self, VALUE recorder_
926
914
 
927
915
  static VALUE _native_track_object(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE new_obj, VALUE weight, VALUE alloc_class) {
928
916
  ENFORCE_TYPE(weight, T_FIXNUM);
929
- ddog_CharSlice alloc_class_slice = char_slice_from_ruby_string(alloc_class);
930
- track_object(recorder_instance, new_obj, NUM2UINT(weight), &alloc_class_slice);
917
+ track_object(recorder_instance, new_obj, NUM2UINT(weight), char_slice_from_ruby_string(alloc_class));
931
918
  return Qtrue;
932
919
  }
933
920
 
@@ -26,6 +26,6 @@ typedef struct {
26
26
 
27
27
  void record_sample(VALUE recorder_instance, ddog_prof_Slice_Location locations, sample_values values, sample_labels labels);
28
28
  void record_endpoint(VALUE recorder_instance, uint64_t local_root_span_id, ddog_CharSlice endpoint);
29
- void track_object(VALUE recorder_instance, VALUE new_object, unsigned int sample_weight, ddog_CharSlice *alloc_class);
29
+ void track_object(VALUE recorder_instance, VALUE new_object, unsigned int sample_weight, ddog_CharSlice alloc_class);
30
30
  void recorder_after_gc_step(VALUE recorder_instance);
31
31
  VALUE enforce_recorder_instance(VALUE object);
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AppSec
5
+ # this module encapsulates functions for handling actions that libddawf returns
6
+ module ActionsHandler
7
+ module_function
8
+
9
+ def handle(actions_hash)
10
+ # handle actions according their precedence
11
+ # stack and schema generation should be done before we throw an interrupt signal
12
+ generate_stack(actions_hash['generate_stack']) if actions_hash.key?('generate_stack')
13
+ generate_schema(actions_hash['generate_schema']) if actions_hash.key?('generate_schema')
14
+ interrupt_execution(actions_hash['redirect_request']) if actions_hash.key?('redirect_request')
15
+ interrupt_execution(actions_hash['block_request']) if actions_hash.key?('block_request')
16
+ end
17
+
18
+ def interrupt_execution(action_params)
19
+ throw(Datadog::AppSec::Ext::INTERRUPT, action_params)
20
+ end
21
+
22
+ def generate_stack(_action_params); end
23
+
24
+ def generate_schema(_action_params); end
25
+ end
26
+ end
27
+ end
@@ -3,6 +3,7 @@
3
3
  require_relative 'processor'
4
4
  require_relative 'processor/rule_merger'
5
5
  require_relative 'processor/rule_loader'
6
+ require_relative 'actions_handler'
6
7
 
7
8
  module Datadog
8
9
  module AppSec
@@ -23,7 +24,7 @@ module Datadog
23
24
  devise_integration = Datadog::AppSec::Contrib::Devise::Integration.new
24
25
  settings.appsec.instrument(:devise) unless devise_integration.patcher.patched?
25
26
 
26
- new(processor: processor)
27
+ new(processor, telemetry)
27
28
  end
28
29
 
29
30
  private
@@ -72,21 +73,26 @@ module Datadog
72
73
  end
73
74
  end
74
75
 
75
- attr_reader :processor
76
+ attr_reader :processor, :telemetry
76
77
 
77
- def initialize(processor:)
78
+ def initialize(processor, telemetry)
78
79
  @processor = processor
80
+ @telemetry = telemetry
81
+
79
82
  @mutex = Mutex.new
80
83
  end
81
84
 
82
85
  def reconfigure(ruleset:, telemetry:)
83
86
  @mutex.synchronize do
84
- new = Processor.new(ruleset: ruleset, telemetry: telemetry)
87
+ new_processor = Processor.new(ruleset: ruleset, telemetry: telemetry)
88
+
89
+ if new_processor && new_processor.ready?
90
+ old_processor = @processor
91
+
92
+ @telemetry = telemetry
93
+ @processor = new_processor
85
94
 
86
- if new && new.ready?
87
- old = @processor
88
- @processor = new
89
- old.finalize if old
95
+ old_processor.finalize if old_processor
90
96
  end
91
97
  end
92
98
  end
@@ -49,6 +49,15 @@ module Datadog
49
49
  end
50
50
  end
51
51
 
52
+ # RASP or Runtime Application Self-Protection
53
+ # is a collection of techniques and heuristics aimed at detecting malicious inputs and preventing
54
+ # any potential side-effects on the application resulting from the use of said malicious inputs.
55
+ option :rasp_enabled do |o|
56
+ o.type :bool, nilable: true
57
+ o.env 'DD_APPSEC_RASP_ENABLED'
58
+ o.default true
59
+ end
60
+
52
61
  option :ruleset do |o|
53
62
  o.env 'DD_APPSEC_RULES'
54
63
  o.default :recommended