get_context_lines rescans decompressed embedded source from start per frame with no line-number bound
The object_file_max_decompressed_source_size cap bounds memory but not downstream CPU cost. get_context_lines traverses from the start of the source string for every stack frame via source.lines().skip(start_line), so a crafted debug file inflating to the 1 GiB limit plus a request with many frames referencing a high line number causes O(frames · source_size) CPU work.
Evidence
Config::default() sets object_file_max_decompressed_source_size to 1 GiB (crates/symbolicator-service/src/config.rs:664).
parse_object_options() forwards this limit into symbolic::debuginfo::ParseObjectOptions (crates/symbolicator-service/src/config.rs:578).
- During native source-context application (
crates/symbolicator-native/src/symbolication/source_context.rs:39), module_lookup::try_set_source_context is called once per frame (crates/symbolicator-native/src/symbolication/module_lookup.rs:440).
- For embedded sources (
source_descriptor.contents()), try_set_source_context returns None after calling set_source_context(text, frame), so the frame is not added to the remote_sources grouping HashMap — unlike remote sources, embedded sources are never deduplicated (module_lookup.rs:469–472).
set_source_context invokes get_context_lines with frame.lineno (resolved from the debug file's symcache) (module_lookup.rs:479).
get_context_lines executes source.lines().skip(start_line) (crates/symbolicator-service/src/source_context.rs:23), causing a linear scan from the start of the source for every frame.
- No caller clamps
lineno to the source's actual line count, and no memoization exists, so each frame pays a scan cost proportional to the 1 GiB cap.
- For direct
/symbolicate requests, the number of frames is bounded only by symbolicate_body_max_bytes (config.rs:538), yielding super-linear CPU amplification.
Identified by Warden · wrdn-dos-review · KJC-7SA
Originally posted by @sentry-warden[bot] in #2022 (comment)
get_context_linesrescans decompressed embedded source from start per frame with no line-number boundThe
object_file_max_decompressed_source_sizecap bounds memory but not downstream CPU cost.get_context_linestraverses from the start of the source string for every stack frame viasource.lines().skip(start_line), so a crafted debug file inflating to the 1 GiB limit plus a request with many frames referencing a high line number causes O(frames · source_size) CPU work.Evidence
Config::default()setsobject_file_max_decompressed_source_sizeto1 GiB(crates/symbolicator-service/src/config.rs:664).parse_object_options()forwards this limit intosymbolic::debuginfo::ParseObjectOptions(crates/symbolicator-service/src/config.rs:578).crates/symbolicator-native/src/symbolication/source_context.rs:39),module_lookup::try_set_source_contextis called once per frame (crates/symbolicator-native/src/symbolication/module_lookup.rs:440).source_descriptor.contents()),try_set_source_contextreturnsNoneafter callingset_source_context(text, frame), so the frame is not added to theremote_sourcesgrouping HashMap — unlike remote sources, embedded sources are never deduplicated (module_lookup.rs:469–472).set_source_contextinvokesget_context_lineswithframe.lineno(resolved from the debug file's symcache) (module_lookup.rs:479).get_context_linesexecutessource.lines().skip(start_line)(crates/symbolicator-service/src/source_context.rs:23), causing a linear scan from the start of the source for every frame.linenoto the source's actual line count, and no memoization exists, so each frame pays a scan cost proportional to the 1 GiB cap./symbolicaterequests, the number of frames is bounded only bysymbolicate_body_max_bytes(config.rs:538), yielding super-linear CPU amplification.Identified by Warden · wrdn-dos-review · KJC-7SA
Originally posted by @sentry-warden[bot] in #2022 (comment)