Skip to content

Commit 848dcda

Browse files
committed
invoke waf for curl requests
1 parent b971805 commit 848dcda

File tree

29 files changed

+5158
-53
lines changed

29 files changed

+5158
-53
lines changed

appsec/cmake/extension.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ target_link_libraries(extension PRIVATE zai)
3333
target_link_libraries(extension PRIVATE mpack PhpConfig zai rapidjson_appsec)
3434
target_include_directories(extension PRIVATE ..)
3535

36-
# we don't have any C++ now, but just so we don't forget in the future...
36+
# gnu unique prevents shared libraries from being unloaded from memory by dlclose
3737
check_cxx_compiler_flag("-fno-gnu-unique" COMPILER_HAS_NO_GNU_UNIQUE)
3838
if(COMPILER_HAS_NO_GNU_UNIQUE)
3939
target_compile_options(extension PRIVATE $<$<COMPILE_LANGUAGE:CXX>:-fno-gnu-unique>)

appsec/src/extension/configuration.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ extern bool runtime_config_first_init;
7171
CONFIG(BOOL, DD_APM_TRACING_ENABLED, "true") \
7272
CONFIG(BOOL, DD_API_SECURITY_ENABLED, "true", .ini_change = zai_config_system_ini_change) \
7373
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
// clang-format on
7577

7678
#define CALIAS CONFIG

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"
@@ -512,7 +513,7 @@ static PHP_FUNCTION(datadog_appsec_push_addresses)
512513
if (!DDAPPSEC_G(active)) {
513514
mlog(dd_log_debug, "Trying to access to push_addresses "
514515
"function while appsec is disabled");
515-
return;
516+
RETURN_FALSE;
516517
}
517518

518519
zend_array *addresses = NULL;
@@ -574,13 +575,13 @@ static PHP_FUNCTION(datadog_appsec_push_addresses)
574575

575576
if (opts.rasp_rule && ZSTR_LEN(opts.rasp_rule) > 0 &&
576577
!get_global_DD_APPSEC_RASP_ENABLED()) {
577-
return;
578+
RETURN_FALSE;
578579
}
579580

580581
dd_conn *conn = dd_helper_mgr_cur_conn();
581582
if (conn == NULL) {
582583
mlog_g(dd_log_debug, "No connection; skipping push_addresses");
583-
return;
584+
RETURN_FALSE;
584585
}
585586

586587
dd_result res = dd_request_exec(conn, addresses, &opts);
@@ -609,6 +610,8 @@ static PHP_FUNCTION(datadog_appsec_push_addresses)
609610
dd_request_abort_redirect();
610611
}
611612
}
613+
614+
RETURN_TRUE;
612615
}
613616

614617
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(
@@ -623,7 +626,7 @@ ZEND_ARG_INFO_WITH_DEFAULT_VALUE(0, "subctx_last_call", false)
623626
ZEND_END_ARG_INFO()
624627

625628
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(
626-
push_addresses_arginfo, 0, 0, IS_VOID, 1)
629+
push_addresses_arginfo, 0, 0, _IS_BOOL, 1)
627630
ZEND_ARG_INFO(0, addresses)
628631
ZEND_ARG_INFO_WITH_DEFAULT_VALUE(0, rasp_rule_or_opts, NULL)
629632
ZEND_END_ARG_INFO()
@@ -648,6 +651,7 @@ static const zend_function_entry testing_functions[] = {
648651
static void _register_testing_objects(void)
649652
{
650653
dd_phpobj_reg_funcs(functions);
654+
dd_curl_register_functions();
651655

652656
if (!get_global_DD_APPSEC_TESTING()) {
653657
return;

appsec/src/extension/entity_body.c

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,7 @@ void dd_entity_body_startup(void)
6969
_json_decode_ex = php_json_decode_ex;
7070
#endif
7171

72-
if (get_global_DD_APPSEC_TESTING()) {
73-
dd_phpobj_reg_funcs(ext_functions);
74-
}
72+
dd_phpobj_reg_funcs(ext_functions);
7573
}
7674

7775
void dd_entity_body_gshutdown(void)

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: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -410,8 +410,6 @@ zend_array *nonnull dd_request_abort_redirect_spec(void)
410410
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
411411
void _request_abort_static_page(int response_code, int type)
412412
{
413-
SG(sapi_headers).http_response_code = response_code;
414-
415413
dd_response_type response_type = type;
416414
if (response_type == response_type_auto) {
417415
zval *server =
@@ -446,6 +444,8 @@ void _request_abort_static_page(int response_code, int type)
446444
return;
447445
}
448446

447+
SG(sapi_headers).http_response_code = response_code;
448+
449449
_set_content_type(content_type);
450450
_set_output_zstr(body);
451451
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;
@@ -53,6 +54,9 @@ static THREAD_LOCAL_ON_ZTS bool _empty_service_or_env;
5354
#define MAX_LENGTH_OF_REM_CFG_PATH 31
5455
static THREAD_LOCAL_ON_ZTS char
5556
_last_rem_cfg_path[MAX_LENGTH_OF_REM_CFG_PATH + 1];
57+
static THREAD_LOCAL_ON_ZTS uint64_t _downstream_body_count_this_req;
58+
static THREAD_LOCAL_ON_ZTS uint64_t _downstream_body_count_total;
59+
5660
#define CLIENT_IP_LOOKUP_FAILED ((zend_string *)-1)
5761

5862
bool dd_req_is_user_req(void) { return _enabled_user_req; }
@@ -82,8 +86,10 @@ void dd_req_lifecycle_startup(void)
8286
}
8387

8488
_server_zstr = zend_string_init_interned(LSTRARG("_SERVER"), 1);
89+
_dd_downstream_request_metric =
90+
zend_string_init_interned(LSTRARG("_dd.appsec.downstream_request"), 1);
8591

86-
_register_testing_objects();
92+
_register_functions();
8793
}
8894

8995
void dd_req_lifecycle_rinit(bool force)
@@ -181,6 +187,8 @@ static zend_array *nullable _do_request_begin(
181187

182188
dd_tags_rinit();
183189

190+
_downstream_body_count_this_req = 0;
191+
184192
zend_string *nullable rbe = NULL;
185193
if (rbe_zv) {
186194
rbe = _get_entity_as_string(rbe_zv);
@@ -267,6 +275,21 @@ void dd_req_lifecycle_rshutdown(bool ignore_verdict, bool force)
267275
return;
268276
}
269277

278+
// RFC-1062: Set downstream request metric if any bodies were analyzed
279+
if (_cur_req_span && _downstream_body_count_this_req > 0) {
280+
zval *metrics_zv = dd_trace_span_get_metrics(_cur_req_span);
281+
if (metrics_zv) {
282+
zval zv;
283+
ZVAL_DOUBLE(&zv, 1.0);
284+
zend_hash_update(
285+
Z_ARRVAL_P(metrics_zv), _dd_downstream_request_metric, &zv);
286+
mlog_g(dd_log_debug,
287+
"Set _dd.appsec.downstream_request metric (analyzed %" PRIu64
288+
" bodies)",
289+
_downstream_body_count_this_req);
290+
}
291+
}
292+
270293
if (_enabled_user_req) {
271294
if (_cur_req_span) {
272295
mlog_g(dd_log_info,
@@ -967,20 +990,74 @@ PHP_FUNCTION(datadog_appsec_testing_dump_req_lifecycle_state)
967990
}
968991
}
969992

993+
static PHP_FUNCTION(datadog_appsec_should_send_downstream_bodies)
994+
{
995+
if (zend_parse_parameters_none() == FAILURE) {
996+
RETURN_FALSE;
997+
}
998+
999+
if (!DDAPPSEC_G(active)) {
1000+
RETURN_FALSE;
1001+
}
1002+
1003+
_downstream_body_count_total++;
1004+
1005+
if (_downstream_body_count_this_req >=
1006+
(uint64_t)get_DD_API_SECURITY_MAX_DOWNSTREAM_REQUEST_BODY_ANALYSIS()) {
1007+
mlog_g(dd_log_debug,
1008+
"Downstream body count limit reached for this request (did for "
1009+
"%" PRIu64 " requests)",
1010+
_downstream_body_count_this_req);
1011+
RETURN_FALSE;
1012+
}
1013+
1014+
double sample_rate =
1015+
get_DD_API_SECURITY_DOWNSTREAM_BODY_ANALYSIS_SAMPLE_RATE();
1016+
if (sample_rate >= 1.0) {
1017+
mlog_g(dd_log_debug, "Downstream body analysis sample rate is 1.0; "
1018+
"sending all downstream bodies up to the limit");
1019+
_downstream_body_count_this_req++;
1020+
RETURN_TRUE;
1021+
}
1022+
1023+
static const uint64_t KNUTH_FACTOR = 11400714819323198549ULL;
1024+
uint64_t threshold = (uint64_t)(sample_rate * (double)UINT64_MAX);
1025+
uint64_t sample_value = _downstream_body_count_total * KNUTH_FACTOR;
1026+
1027+
if (sample_value < threshold) {
1028+
_downstream_body_count_this_req++;
1029+
mlog_g(dd_log_debug,
1030+
"We're sending the bodies of this request for analysis");
1031+
RETURN_TRUE;
1032+
}
1033+
1034+
mlog_g(dd_log_debug,
1035+
"We're NOT sending the bodies of this request for analysis");
1036+
RETURN_FALSE;
1037+
}
1038+
9701039
// clang-format off
9711040
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(dump_arginfo, 0, 0, IS_ARRAY, 0)
9721041
ZEND_END_ARG_INFO()
1042+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(dump_downstream_bodies_arginfo, 0, 0, _IS_BOOL, 0)
1043+
ZEND_END_ARG_INFO()
9731044

9741045
static const zend_function_entry functions[] = {
1046+
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)
1047+
PHP_FE_END
1048+
};
1049+
1050+
static const zend_function_entry test_functions[] = {
9751051
ZEND_RAW_FENTRY(DD_TESTING_NS "dump_req_lifecycle_state", PHP_FN(datadog_appsec_testing_dump_req_lifecycle_state), dump_arginfo, 0, NULL, NULL)
9761052
PHP_FE_END
9771053
};
9781054
// clang-format on
9791055

980-
static void _register_testing_objects(void)
1056+
static void _register_functions(void)
9811057
{
982-
if (!get_global_DD_APPSEC_TESTING()) {
983-
return;
984-
}
9851058
dd_phpobj_reg_funcs(functions);
1059+
1060+
if (get_global_DD_APPSEC_TESTING()) {
1061+
dd_phpobj_reg_funcs(test_functions);
1062+
}
9861063
}

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)