@@ -39,10 +39,11 @@ static void _reset_globals(void);
3939const zend_array * nonnull _get_server_equiv (
4040 const zend_array * nonnull superglob_equiv );
4141static 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
4444static bool _enabled_user_req ;
4545static zend_string * _server_zstr ;
46+ static zend_string * _dd_downstream_request_metric ;
4647
4748static THREAD_LOCAL_ON_ZTS zend_object * nullable _cur_req_span ;
4849static 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
5556static 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
5963bool 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
94100void 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
10861155ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX (dump_arginfo , 0 , 0 , IS_ARRAY , 0 )
10871156ZEND_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
10891160static 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}
0 commit comments