datadog 2.9.0 → 2.10.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|