Skip to content

Commit 192a4a1

Browse files
committed
invoke waf for curl requests
1 parent e7e689d commit 192a4a1

File tree

26 files changed

+5153
-47
lines changed

26 files changed

+5153
-47
lines changed

appsec/src/extension/configuration.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ extern bool runtime_config_first_init;
7070
CONFIG(STRING, DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON, "") \
7171
CONFIG(BOOL, DD_APM_TRACING_ENABLED, "true") \
7272
CONFIG(BOOL, DD_API_SECURITY_ENABLED, "true", .ini_change = zai_config_system_ini_change) \
73-
CONFIG(DOUBLE, DD_API_SECURITY_SAMPLE_DELAY, "30.0", .ini_change = zai_config_system_ini_change)
73+
CONFIG(DOUBLE, DD_API_SECURITY_SAMPLE_DELAY, "30.0", .ini_change = zai_config_system_ini_change) \
74+
CONFIG(INT, DD_API_SECURITY_MAX_DOWNSTREAM_REQUEST_BODY_ANALYSIS, "1") \
75+
CONFIG(DOUBLE, DD_API_SECURITY_DOWNSTREAM_BODY_ANALYSIS_SAMPLE_RATE, "0.5")
7476

7577
#ifdef __linux__
7678
#define DD_CONFIGURATION \

appsec/src/extension/curl.c

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Unless explicitly stated otherwise all files in this repository are
2+
// dual-licensed under the Apache-2.0 License or BSD-3-Clause License.
3+
//
4+
// This product includes software developed at Datadog
5+
// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.
6+
7+
// clang-format off
8+
#include <php.h> // (must come before php_streams.h)
9+
// clang-format on
10+
#include <main/php_streams.h>
11+
12+
#include "compatibility.h"
13+
#include "php_compat.h"
14+
#include "php_objects.h"
15+
16+
static PHP_FUNCTION(datadog_appsec_fflush_stdiocast)
17+
{
18+
zval *stream_zv;
19+
20+
ZEND_PARSE_PARAMETERS_START(1, 1)
21+
Z_PARAM_RESOURCE(stream_zv)
22+
ZEND_PARSE_PARAMETERS_END();
23+
24+
php_stream *stream = NULL;
25+
php_stream_from_zval(stream, stream_zv);
26+
27+
if (stream->stdiocast != NULL) {
28+
int result = fflush(stream->stdiocast);
29+
RETURN_BOOL(result == 0);
30+
}
31+
32+
RETURN_TRUE;
33+
}
34+
35+
// clang-format off
36+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(fflush_stdiocast_arginfo, 0, 1, _IS_BOOL, 0)
37+
ZEND_ARG_INFO(0, stream)
38+
ZEND_END_ARG_INFO()
39+
40+
static const zend_function_entry functions[] = {
41+
ZEND_RAW_FENTRY(DD_APPSEC_NS "fflush_stdiocast", PHP_FN(datadog_appsec_fflush_stdiocast), fflush_stdiocast_arginfo, 0, NULL, NULL)
42+
PHP_FE_END
43+
};
44+
// clang-format on
45+
46+
void dd_curl_register_functions(void) { dd_phpobj_reg_funcs(functions); }

appsec/src/extension/curl.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Unless explicitly stated otherwise all files in this repository are
2+
// dual-licensed under the Apache-2.0 License or BSD-3-Clause License.
3+
//
4+
// This product includes software developed at Datadog
5+
// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.
6+
#pragma once
7+
8+
void dd_curl_register_functions(void);
9+

appsec/src/extension/ddappsec.c

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "commands_ctx.h"
2929
#include "compatibility.h"
3030
#include "configuration.h"
31+
#include "curl.h"
3132
#include "ddappsec.h"
3233
#include "dddefs.h"
3334
#include "ddtrace.h"
@@ -515,7 +516,7 @@ static PHP_FUNCTION(datadog_appsec_push_addresses)
515516
if (!DDAPPSEC_G(active)) {
516517
mlog(dd_log_debug, "Trying to access to push_addresses "
517518
"function while appsec is disabled");
518-
return;
519+
RETURN_FALSE;
519520
}
520521

521522
zend_array *addresses = NULL;
@@ -577,13 +578,13 @@ static PHP_FUNCTION(datadog_appsec_push_addresses)
577578

578579
if (opts.rasp_rule && ZSTR_LEN(opts.rasp_rule) > 0 &&
579580
!get_global_DD_APPSEC_RASP_ENABLED()) {
580-
return;
581+
RETURN_FALSE;
581582
}
582583

583584
dd_conn *conn = dd_helper_mgr_cur_conn();
584585
if (conn == NULL) {
585586
mlog_g(dd_log_debug, "No connection; skipping push_addresses");
586-
return;
587+
RETURN_FALSE;
587588
}
588589

589590
struct block_params block_params = {0};
@@ -603,6 +604,8 @@ static PHP_FUNCTION(datadog_appsec_push_addresses)
603604

604605
dd_req_lifecycle_abort(REQUEST_STAGE_MID_REQUEST, res, &block_params);
605606
dd_request_abort_destroy_block_params(&block_params);
607+
608+
RETURN_TRUE;
606609
}
607610

608611
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(
@@ -617,7 +620,7 @@ ZEND_ARG_INFO_WITH_DEFAULT_VALUE(0, "subctx_last_call", false)
617620
ZEND_END_ARG_INFO()
618621

619622
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(
620-
push_addresses_arginfo, 0, 0, IS_VOID, 1)
623+
push_addresses_arginfo, 0, 0, _IS_BOOL, 1)
621624
ZEND_ARG_INFO(0, addresses)
622625
ZEND_ARG_INFO_WITH_DEFAULT_VALUE(0, rasp_rule_or_opts, NULL)
623626
ZEND_END_ARG_INFO()
@@ -642,6 +645,7 @@ static const zend_function_entry testing_functions[] = {
642645
static void _register_testing_objects(void)
643646
{
644647
dd_phpobj_reg_funcs(functions);
648+
dd_curl_register_functions();
645649

646650
if (!get_global_DD_APPSEC_TESTING()) {
647651
return;

appsec/src/extension/entity_body.c

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,7 @@ void dd_entity_body_startup(void)
7272

7373
dd_xml_parser_startup();
7474

75-
if (get_global_DD_APPSEC_TESTING()) {
76-
dd_phpobj_reg_funcs(ext_functions);
77-
}
75+
dd_phpobj_reg_funcs(ext_functions);
7876
}
7977

8078
void dd_entity_body_shutdown(void) { dd_xml_parser_shutdown(); }

appsec/src/extension/json_truncated_parser.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ class TruncatedJsonInputStream {
9090
public:
9191
using Ch = char;
9292

93-
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
9493
TruncatedJsonInputStream(
94+
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
9595
const char *data, std::size_t length, std::size_t size_limit = SIZE_MAX)
9696
: data_{data}, length_{length}, size_limit_{size_limit}
9797
{}
@@ -189,9 +189,9 @@ class ToZvalHandler {
189189
return AddValue(val);
190190
}
191191

192-
// NOLINTNEXTLINE(readability-convert-member-functions-to-static,readability-named-parameter)
193-
auto RawNumber(
194-
const TruncatedJsonInputStream::Ch *, rapidjson::SizeType, bool) -> bool
192+
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
193+
auto RawNumber(const TruncatedJsonInputStream::Ch * /* unused */,
194+
rapidjson::SizeType /* unused */, bool /* unused */) -> bool
195195
{
196196
assert("RawNumber should not be called (requires flags we are not "
197197
"using)" == nullptr);

appsec/src/extension/request_abort.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -364,10 +364,6 @@ zend_array *nonnull dd_request_abort_redirect_spec(
364364
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
365365
void _request_abort_static_page(struct block_params *nonnull block_params)
366366
{
367-
SG(sapi_headers).http_response_code = block_params->response_code
368-
? block_params->response_code
369-
: DEFAULT_BLOCKING_RESPONSE_CODE;
370-
371367
dd_response_type response_type = block_params->response_type;
372368
if (response_type == response_type_auto) {
373369
zval *server =
@@ -402,6 +398,10 @@ void _request_abort_static_page(struct block_params *nonnull block_params)
402398
return;
403399
}
404400

401+
SG(sapi_headers).http_response_code = block_params->response_code
402+
? block_params->response_code
403+
: DEFAULT_BLOCKING_RESPONSE_CODE;
404+
405405
_set_content_type(content_type);
406406
_set_output_zstr(body);
407407
zend_string_release(body);

appsec/src/extension/request_lifecycle.c

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,11 @@ static void _reset_globals(void);
3939
const zend_array *nonnull _get_server_equiv(
4040
const zend_array *nonnull superglob_equiv);
4141
static uint64_t _calc_sampling_key(zend_object *root_span, int status_code);
42-
static void _register_testing_objects(void);
42+
static void _register_functions(void);
4343

4444
static bool _enabled_user_req;
4545
static zend_string *_server_zstr;
46+
static zend_string *_dd_downstream_request_metric;
4647

4748
static THREAD_LOCAL_ON_ZTS zend_object *nullable _cur_req_span;
4849
static THREAD_LOCAL_ON_ZTS zend_array *nullable _superglob_equiv;
@@ -54,6 +55,9 @@ static THREAD_LOCAL_ON_ZTS bool _request_blocked;
5455
#define MAX_LENGTH_OF_REM_CFG_PATH 31
5556
static THREAD_LOCAL_ON_ZTS char
5657
_last_rem_cfg_path[MAX_LENGTH_OF_REM_CFG_PATH + 1];
58+
static THREAD_LOCAL_ON_ZTS uint64_t _downstream_body_count_this_req;
59+
static THREAD_LOCAL_ON_ZTS uint64_t _downstream_body_count_total;
60+
5761
#define CLIENT_IP_LOOKUP_FAILED ((zend_string *)-1)
5862

5963
bool dd_req_is_user_req(void) { return _enabled_user_req; }
@@ -87,8 +91,10 @@ void dd_req_lifecycle_startup(void)
8791
}
8892

8993
_server_zstr = zend_string_init_interned(LSTRARG("_SERVER"), 1);
94+
_dd_downstream_request_metric =
95+
zend_string_init_interned(LSTRARG("_dd.appsec.downstream_request"), 1);
9096

91-
_register_testing_objects();
97+
_register_functions();
9298
}
9399

94100
void dd_req_lifecycle_rinit(bool force)
@@ -193,6 +199,8 @@ static zend_array *nullable _do_request_begin(
193199

194200
dd_tags_rinit();
195201

202+
_downstream_body_count_this_req = 0;
203+
196204
zend_string *nullable rbe = NULL;
197205
if (rbe_zv) {
198206
rbe = _get_entity_as_string(rbe_zv);
@@ -272,6 +280,21 @@ void dd_req_lifecycle_rshutdown(bool ignore_verdict, bool force)
272280
return;
273281
}
274282

283+
// RFC-1062: Set downstream request metric if any bodies were analyzed
284+
if (_cur_req_span && _downstream_body_count_this_req > 0) {
285+
zval *metrics_zv = dd_trace_span_get_metrics(_cur_req_span);
286+
if (metrics_zv) {
287+
zval zv;
288+
ZVAL_DOUBLE(&zv, 1.0);
289+
zend_hash_update(
290+
Z_ARRVAL_P(metrics_zv), _dd_downstream_request_metric, &zv);
291+
mlog_g(dd_log_debug,
292+
"Set _dd.appsec.downstream_request metric (analyzed %" PRIu64
293+
" bodies)",
294+
_downstream_body_count_this_req);
295+
}
296+
}
297+
275298
if (_enabled_user_req) {
276299
if (_cur_req_span) {
277300
mlog_g(dd_log_info,
@@ -1082,20 +1105,74 @@ PHP_FUNCTION(datadog_appsec_testing_dump_req_lifecycle_state)
10821105
}
10831106
}
10841107

1108+
static PHP_FUNCTION(datadog_appsec_should_send_downstream_bodies)
1109+
{
1110+
if (zend_parse_parameters_none() == FAILURE) {
1111+
RETURN_FALSE;
1112+
}
1113+
1114+
if (!DDAPPSEC_G(active)) {
1115+
RETURN_FALSE;
1116+
}
1117+
1118+
_downstream_body_count_total++;
1119+
1120+
if (_downstream_body_count_this_req >=
1121+
(uint64_t)get_DD_API_SECURITY_MAX_DOWNSTREAM_REQUEST_BODY_ANALYSIS()) {
1122+
mlog_g(dd_log_debug,
1123+
"Downstream body count limit reached for this request (did for "
1124+
"%" PRIu64 " requests)",
1125+
_downstream_body_count_this_req);
1126+
RETURN_FALSE;
1127+
}
1128+
1129+
double sample_rate =
1130+
get_DD_API_SECURITY_DOWNSTREAM_BODY_ANALYSIS_SAMPLE_RATE();
1131+
if (sample_rate >= 1.0) {
1132+
mlog_g(dd_log_debug, "Downstream body analysis sample rate is 1.0; "
1133+
"sending all downstream bodies up to the limit");
1134+
_downstream_body_count_this_req++;
1135+
RETURN_TRUE;
1136+
}
1137+
1138+
static const uint64_t KNUTH_FACTOR = 11400714819323198549ULL;
1139+
uint64_t threshold = (uint64_t)(sample_rate * (double)UINT64_MAX);
1140+
uint64_t sample_value = _downstream_body_count_total * KNUTH_FACTOR;
1141+
1142+
if (sample_value < threshold) {
1143+
_downstream_body_count_this_req++;
1144+
mlog_g(dd_log_debug,
1145+
"We're sending the bodies of this request for analysis");
1146+
RETURN_TRUE;
1147+
}
1148+
1149+
mlog_g(dd_log_debug,
1150+
"We're NOT sending the bodies of this request for analysis");
1151+
RETURN_FALSE;
1152+
}
1153+
10851154
// clang-format off
10861155
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(dump_arginfo, 0, 0, IS_ARRAY, 0)
10871156
ZEND_END_ARG_INFO()
1157+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(dump_downstream_bodies_arginfo, 0, 0, _IS_BOOL, 0)
1158+
ZEND_END_ARG_INFO()
10881159

10891160
static const zend_function_entry functions[] = {
1161+
ZEND_RAW_FENTRY(DD_APPSEC_NS "should_send_downstream_bodies", PHP_FN(datadog_appsec_should_send_downstream_bodies), dump_downstream_bodies_arginfo, 0, NULL, NULL)
1162+
PHP_FE_END
1163+
};
1164+
1165+
static const zend_function_entry test_functions[] = {
10901166
ZEND_RAW_FENTRY(DD_TESTING_NS "dump_req_lifecycle_state", PHP_FN(datadog_appsec_testing_dump_req_lifecycle_state), dump_arginfo, 0, NULL, NULL)
10911167
PHP_FE_END
10921168
};
10931169
// clang-format on
10941170

1095-
static void _register_testing_objects(void)
1171+
static void _register_functions(void)
10961172
{
1097-
if (!get_global_DD_APPSEC_TESTING()) {
1098-
return;
1099-
}
11001173
dd_phpobj_reg_funcs(functions);
1174+
1175+
if (get_global_DD_APPSEC_TESTING()) {
1176+
dd_phpobj_reg_funcs(test_functions);
1177+
}
11011178
}

appsec/src/helper/parameter_base.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class parameter_base {
4141
[[nodiscard]] parameter_type type() const noexcept
4242
{
4343
switch (ddwaf_object_get_type(&obj_)) {
44+
default:
4445
case DDWAF_OBJ_INVALID:
4546
return parameter_type::invalid;
4647
case DDWAF_OBJ_NULL:

0 commit comments

Comments
 (0)