Skip to content

Commit 4f0bcb5

Browse files
chore(asm): clean libddwaf loading (#12102)
Depending of the timing, libddwaf loading process could create triggers that would create loops in our instrumentation. From what I investigated: - if loaded too early, it could have bad interactions with gevent. - if loaded too late, it could be self instrumented by the tracer, creating a loop, as ctypes is using Popen and subprocess. while keeping the late loading introduced by 2 previous PRs - #11987 - #12013 this PR introduced a mechanism to bypass tracer instrumentation during ctypes loading, to avoid a possible loop that would prevent the WAF to be loaded. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)
1 parent 16d5280 commit 4f0bcb5

File tree

4 files changed

+24
-12
lines changed

4 files changed

+24
-12
lines changed

ddtrace/appsec/_ddwaf/ddwaf_types.py

+3
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,12 @@
2222

2323
if system() == "Linux":
2424
try:
25+
asm_config._bypass_instrumentation_for_waf = True
2526
ctypes.CDLL(ctypes.util.find_library("rt"), mode=ctypes.RTLD_GLOBAL)
2627
except Exception: # nosec
2728
pass
29+
finally:
30+
asm_config._bypass_instrumentation_for_waf = False
2831

2932
ARCHI = machine().lower()
3033

ddtrace/appsec/_processor.py

+14-12
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,19 @@
44
from json.decoder import JSONDecodeError
55
import os
66
import os.path
7+
from typing import TYPE_CHECKING
78
from typing import Any
89
from typing import Dict
910
from typing import List
1011
from typing import Optional
1112
from typing import Set
1213
from typing import Tuple
1314
from typing import Union
15+
16+
17+
if TYPE_CHECKING:
18+
import ddtrace.appsec._ddwaf as ddwaf
19+
1420
import weakref
1521

1622
from ddtrace._trace.processor import SpanProcessor
@@ -167,14 +173,17 @@ def __post_init__(self) -> None:
167173
def delayed_init(self) -> None:
168174
try:
169175
if self._rules is not None and not hasattr(self, "_ddwaf"):
170-
self._ddwaf = ddwaf.DDWaf(
176+
from ddtrace.appsec._ddwaf import DDWaf # noqa: E402
177+
import ddtrace.appsec._metrics as metrics # noqa: E402
178+
179+
self.metrics = metrics
180+
self._ddwaf = DDWaf(
171181
self._rules, self.obfuscation_parameter_key_regexp, self.obfuscation_parameter_value_regexp
172182
)
173-
_set_waf_init_metric(self._ddwaf.info)
183+
self.metrics._set_waf_init_metric(self._ddwaf.info)
174184
except Exception:
175185
# Partial of DDAS-0005-00
176186
log.warning("[DDAS-0005-00] WAF initialization failed")
177-
raise
178187
self._update_required()
179188

180189
def _update_required(self):
@@ -193,7 +202,7 @@ def _update_rules(self, new_rules: Dict[str, Any]) -> bool:
193202
if asm_config._asm_static_rule_file is not None:
194203
return result
195204
result = self._ddwaf.update_rules(new_rules)
196-
_set_waf_updates_metric(self._ddwaf.info)
205+
self.metrics._set_waf_updates_metric(self._ddwaf.info)
197206
self._update_required()
198207
return result
199208

@@ -241,7 +250,7 @@ def waf_callable(custom_data=None, **kwargs):
241250
return self._waf_action(span._local_root or span, ctx, custom_data, **kwargs)
242251

243252
_asm_request_context.set_waf_callback(waf_callable)
244-
_asm_request_context.add_context_callback(_set_waf_request_metrics)
253+
_asm_request_context.add_context_callback(self.metrics._set_waf_request_metrics)
245254
if headers is not None:
246255
_asm_request_context.set_waf_address(SPAN_DATA_NAMES.REQUEST_HEADERS_NO_COOKIES, headers)
247256
_asm_request_context.set_waf_address(
@@ -436,10 +445,3 @@ def on_span_finish(self, span: Span) -> None:
436445
del self._span_to_waf_ctx[s]
437446
except Exception: # nosec B110
438447
pass
439-
440-
441-
# load waf at the end only to avoid possible circular imports with gevent
442-
import ddtrace.appsec._ddwaf as ddwaf # noqa: E402
443-
from ddtrace.appsec._metrics import _set_waf_init_metric # noqa: E402
444-
from ddtrace.appsec._metrics import _set_waf_request_metrics # noqa: E402
445-
from ddtrace.appsec._metrics import _set_waf_updates_metric # noqa: E402

ddtrace/contrib/internal/subprocess/patch.py

+6
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,8 @@ def unpatch() -> None:
327327
@trace_utils.with_traced_module
328328
def _traced_ossystem(module, pin, wrapped, instance, args, kwargs):
329329
try:
330+
if asm_config._bypass_instrumentation_for_waf:
331+
return wrapped(*args, **kwargs)
330332
if isinstance(args[0], str):
331333
for callback in _STR_CALLBACKS.values():
332334
callback(args[0])
@@ -393,6 +395,8 @@ def _traced_osspawn(module, pin, wrapped, instance, args, kwargs):
393395
@trace_utils.with_traced_module
394396
def _traced_subprocess_init(module, pin, wrapped, instance, args, kwargs):
395397
try:
398+
if asm_config._bypass_instrumentation_for_waf:
399+
return wrapped(*args, **kwargs)
396400
cmd_args = args[0] if len(args) else kwargs["args"]
397401
if isinstance(cmd_args, (list, tuple, str)):
398402
if kwargs.get("shell", False):
@@ -425,6 +429,8 @@ def _traced_subprocess_init(module, pin, wrapped, instance, args, kwargs):
425429
@trace_utils.with_traced_module
426430
def _traced_subprocess_wait(module, pin, wrapped, instance, args, kwargs):
427431
try:
432+
if asm_config._bypass_instrumentation_for_waf:
433+
return wrapped(*args, **kwargs)
428434
binary = core.get_item("subprocess_popen_binary")
429435

430436
with pin.tracer.trace(COMMANDS.SPAN_NAME, resource=binary, span_type=SpanTypes.SYSTEM) as span:

ddtrace/settings/asm.py

+1
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ class ASMConfig(Env):
245245
default=r"^[+-]?((0b[01]+)|(0x[0-9A-Fa-f]+)|(\d+\.?\d*(?:[Ee][+-]?\d+)?|\.\d+(?:[Ee][+-]"
246246
+ r"?\d+)?)|(X\'[0-9A-Fa-f]+\')|(B\'[01]+\'))$",
247247
)
248+
_bypass_instrumentation_for_waf = False
248249

249250
def __init__(self):
250251
super().__init__()

0 commit comments

Comments
 (0)