Skip to content

Changes to systemd journal parser to handle corrupt and binary data#5113

Merged
joachimmetz merged 5 commits into
log2timeline:mainfrom
kev365:bugfix-systemd-journal-robustness
Jul 1, 2026
Merged

Changes to systemd journal parser to handle corrupt and binary data#5113
joachimmetz merged 5 commits into
log2timeline:mainfrom
kev365:bugfix-systemd-journal-robustness

Conversation

@kev365

@kev365 kev365 commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Description

Makes the systemd journal parser resilient to the damaged, rotated and unclean journals that matter most in forensics. Three related robustness fixes:

  • Continue past a corrupt entry instead of returning. The entry loop in ParseFileObject did return on the first ParseError, abandoning every entry after the first unparsable one. It now does continue, matching systemd's reader guidance to skip corruption and recover as much surrounding data as possible.
  • Decode field data at the byte level. _ParseJournalEntry did event_data.decode("utf-8") with no error handling; a non-UTF-8 field value (the on-disk format permits binary DATA payloads) raised an uncaught UnicodeDecodeError — a ValueError, so the surrounding except errors.ParseError never caught it. The split-and-decode is extracted into _SplitFieldData, which splits on the first = byte and decodes with errors="replace" (event-data values must be str).
  • Wrap XZ and LZ4 decompression in try/except → ParseError, as ZSTD already was, so corrupt compressed data raises a ParseError instead of an uncaught library exception.

Related issue: fixes #5109

Testing

  • testParseDirty updated: 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.
  • Added testSplitFieldData covering a text value and a non-UTF-8 value (→ U+FFFD replacement).
  • Validated end-to-end against journalctl: a journal written by systemd-journal-remote with a MESSAGE containing non-UTF-8 bytes parses to a body with U+FFFD, matching journalctl --output=json's byte-array rendering.
  • Existing clean-journal tests (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: 0 entries into a single "reached corrupt tail" warning — let me know your preference.

Checklist

  • No new dependencies are required or l2tdevtools has been updated.
  • Test data has a Plaso compatible license. (No new test data added.)
  • Reviewer assigned.
  • Automated checks (GitHub Actions, AppVeyor) pass.

@codecov

codecov Bot commented Jun 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.46154% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.09%. Comparing base (680976f) to head (4257669).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
plaso/parsers/systemd_journal.py 88.46% 3 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread plaso/parsers/systemd_journal.py Outdated
"""
key, _, value = field_data.partition(b"=")
return (
key.decode("utf-8", errors="replace"),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will silently replace "invalid" characters let's make sure to set an extraction warning if this situation occurs

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@joachimmetz joachimmetz changed the title Make the systemd journal parser resilient to corrupt and binary data Changes to systemd journal parser to handle corrupt and binary data Jun 16, 2026
Comment thread plaso/parsers/systemd_journal.py Outdated
"Unable to decode journal field data as UTF-8, replaced invalid "
"characters"
)
return byte_data.decode("utf-8", errors="replace")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

errors="backslashreplace" might be more appropriate

kev365 and others added 4 commits July 1, 2026 13:59
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>
@joachimmetz joachimmetz force-pushed the bugfix-systemd-journal-robustness branch from dc23560 to ee36088 Compare July 1, 2026 12:09
@joachimmetz joachimmetz added this to the 2026 July release milestone Jul 1, 2026

@joachimmetz joachimmetz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@joachimmetz joachimmetz merged commit fb281ba into log2timeline:main Jul 1, 2026
17 checks passed
@kev365 kev365 deleted the bugfix-systemd-journal-robustness branch July 2, 2026 23:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

systemd journal parser aborts the whole file on one corrupt entry and can crash on non-UTF-8 field values

2 participants