Changes to systemd journal parser to handle corrupt and binary data#5113
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #5113 +/- ##
=======================================
Coverage 85.09% 85.09%
=======================================
Files 455 455
Lines 40393 40411 +18
=======================================
+ Hits 34371 34387 +16
- Misses 6022 6024 +2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
0d64ac9 to
a508457
Compare
| """ | ||
| key, _, value = field_data.partition(b"=") | ||
| return ( | ||
| key.decode("utf-8", errors="replace"), |
There was a problem hiding this comment.
This will silently replace "invalid" characters let's make sure to set an extraction warning if this situation occurs
There was a problem hiding this comment.
Thanks Joachim, here is a proposed adjustment for the extraction warning:
_SplitFieldData now decodes each field via a new _DecodeFieldData, which produces an extraction warning ("Unable to decode journal field data as UTF-8, replaced invalid characters") when invalid UTF-8 is replaced, rather than replacing silently (dc23560). The parser mediator is threaded down (_ParseJournalEntry → _SplitFieldData → _DecodeFieldData). testSplitFieldData asserts the warning fires on a non-UTF-8 value and not on a valid one.
| "Unable to decode journal field data as UTF-8, replaced invalid " | ||
| "characters" | ||
| ) | ||
| return byte_data.decode("utf-8", errors="replace") |
There was a problem hiding this comment.
Unfortunately decode("utf-8", errors="replace") uses the Unicode placeholder character which makes it very hard to determine what the original data was from the extracted data.
It might be better to omit the log line than present an altered one, or better to "escape" the unsupported byte values
There was a problem hiding this comment.
errors="backslashreplace" might be more appropriate
Three robustness fixes that matter for damaged, rotated and unclean journals: - Continue past a corrupt journal entry instead of returning. The parser previously aborted extraction of the entire file after the first unparsable entry; systemd's own format guidance is to skip corruption and recover as much surrounding data as possible. - Decode journal field data at the byte level (split on the first "=" and decode with replacement) so a non-UTF-8 field value no longer raises an uncaught UnicodeDecodeError. Journal field values may contain arbitrary binary data. The split-and-decode is extracted into _SplitFieldData and covered by a unit test for both text and non-UTF-8 values. - Wrap XZ and LZ4 decompression in try/except (as ZSTD already is) so corrupt compressed data raises a ParseError instead of an uncaught library exception. Update the dirty-journal test: the parser now reports all 46 corrupt entries in the unclean tail (1 corrupt pointer + 45 unused objects) instead of only the first. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d UTF-8 Decoding journal field data with errors="replace" silently substituted the Unicode replacement character for invalid UTF-8 bytes. The parser now produces an extraction warning when this happens, so lossy decoding is visible rather than silent. The parser mediator is threaded into the field decode (_ParseJournalEntry -> _SplitFieldData -> new _DecodeFieldData) to do so. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
dc23560 to
ee36088
Compare
Description
Makes the systemd journal parser resilient to the damaged, rotated and unclean journals that matter most in forensics. Three related robustness fixes:
ParseFileObjectdidreturnon the firstParseError, abandoning every entry after the first unparsable one. It now doescontinue, matching systemd's reader guidance to skip corruption and recover as much surrounding data as possible._ParseJournalEntrydidevent_data.decode("utf-8")with no error handling; a non-UTF-8 field value (the on-disk format permits binary DATA payloads) raised an uncaughtUnicodeDecodeError— aValueError, so the surroundingexcept errors.ParseErrornever caught it. The split-and-decode is extracted into_SplitFieldData, which splits on the first=byte and decodes witherrors="replace"(event-data values must bestr).ParseError, as ZSTD already was, so corrupt compressed data raises aParseErrorinstead of an uncaught library exception.Related issue: fixes #5109
Testing
testParseDirtyupdated: the parser now reports all 46 corrupt entries in the unclean tail (1 corrupt entry pointer + 45 unused objects) instead of only the first; the event count (2211) and the first-warning message are unchanged. The 46 = 1 + 45 breakdown is asserted so the count is documented rather than magic.testSplitFieldDatacovering a text value and a non-UTF-8 value (→ U+FFFD replacement).journalctl: a journal written bysystemd-journal-remotewith aMESSAGEcontaining non-UTF-8 bytes parses to a body with U+FFFD, matchingjournalctl --output=json's byte-array rendering.testParse,testParseLZ4,testParseCompactZSTD) unchanged and passing; Black + pylint clean.Open question for the reviewer
On the dirty-journal sample the extraction-warning count rises 1 → 46 because every corrupt tail entry is now surfaced rather than only the first. If that's too noisy, I'm happy to collapse a run of trailing
Unsupported object type: 0entries into a single "reached corrupt tail" warning — let me know your preference.Checklist