datadog 2.9.0 → 2.10.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +27 -1
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +2 -2
- data/ext/datadog_profiling_native_extension/collectors_stack.h +2 -2
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +2 -5
- data/ext/datadog_profiling_native_extension/heap_recorder.c +50 -92
- data/ext/datadog_profiling_native_extension/heap_recorder.h +1 -1
- data/ext/datadog_profiling_native_extension/stack_recorder.c +9 -22
- data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -1
- data/lib/datadog/appsec/actions_handler.rb +27 -0
- data/lib/datadog/appsec/component.rb +14 -8
- data/lib/datadog/appsec/configuration/settings.rb +9 -0
- data/lib/datadog/appsec/context.rb +28 -8
- data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +6 -2
- data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +1 -7
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +4 -5
- data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +15 -12
- data/lib/datadog/appsec/contrib/rack/reactive/request.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/reactive/response.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +3 -3
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +11 -22
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +5 -4
- data/lib/datadog/appsec/contrib/rails/patcher.rb +3 -13
- data/lib/datadog/appsec/contrib/rails/reactive/action.rb +1 -1
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +10 -8
- data/lib/datadog/appsec/contrib/sinatra/patcher.rb +3 -26
- data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +1 -1
- data/lib/datadog/appsec/ext.rb +6 -1
- data/lib/datadog/appsec/metrics/collector.rb +38 -0
- data/lib/datadog/appsec/metrics/exporter.rb +35 -0
- data/lib/datadog/appsec/metrics/telemetry.rb +23 -0
- data/lib/datadog/appsec/metrics.rb +13 -0
- data/lib/datadog/appsec/monitor/gateway/watcher.rb +5 -4
- data/lib/datadog/appsec/monitor/reactive/set_user.rb +1 -1
- data/lib/datadog/appsec/processor.rb +4 -3
- data/lib/datadog/appsec/response.rb +18 -80
- data/lib/datadog/appsec/security_engine/result.rb +67 -0
- data/lib/datadog/appsec/security_engine/runner.rb +88 -0
- data/lib/datadog/appsec/security_engine.rb +9 -0
- data/lib/datadog/appsec.rb +14 -5
- data/lib/datadog/di/component.rb +2 -0
- data/lib/datadog/di/probe_notification_builder.rb +6 -0
- data/lib/datadog/di/redactor.rb +0 -1
- data/lib/datadog/di/remote.rb +26 -5
- data/lib/datadog/tracing/contrib/aws/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/extensions.rb +15 -3
- data/lib/datadog/tracing/contrib/http/integration.rb +3 -0
- data/lib/datadog/version.rb +1 -1
- metadata +32 -18
- data/lib/datadog/appsec/contrib/sinatra/ext.rb +0 -14
- data/lib/datadog/appsec/processor/context.rb +0 -107
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 88fca1176a1b0fe35703ec00277925dd64a377c6736c52674f5aeb3195ec4d86
|
4
|
+
data.tar.gz: 92631bc4c25b6132821d30858e99e8cd8308445f99a258226508ed04f4a696ba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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::
|
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
|
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
|
//
|
@@ -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,
|
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
|
-
|
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
|
-
|
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*,
|
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 =
|
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
|
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
|
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
|
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
|
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
|
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
|
326
|
-
heap_recorder->active_recording
|
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 = (
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
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
|
-
|
368
|
-
return Qnil;
|
369
|
-
}
|
364
|
+
object_record *active_recording = heap_recorder->active_recording;
|
370
365
|
|
371
|
-
|
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 =
|
373
|
+
heap_recorder->active_recording = NULL;
|
381
374
|
|
382
|
-
if (active_recording
|
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
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
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,
|
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
|
-
|
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
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
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
|
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
|
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
|
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
|
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
|
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
|
-
//
|
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->
|
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
|
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
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
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
|