Since 262299dccb compare_locations() falls
through to a realtime-based comparison in compare_boot_ids() if the
compared entries have different boot IDs (and seqnum IDs). This can,
however, cause us to pick up the wrong entry/file on systems with
unreliable/missing RTC, where early-boot entries of a boot might have
earlier realtime timestamp compared to the previous boot.
For example, here's a minimal reproducer consisting of two journal files
containing three entries from two separate boot IDs:
$ journalctl --file one.journal -o export
__CURSOR=s=ba37c62fcacf406aad088166c4963cd1;i=1;b=c220efc91dd440deb89d82c98ddb11a5;m=186a0;t=1e8480;x=39d4323a8e5a248d
__REALTIME_TIMESTAMP=2000000
__MONOTONIC_TIMESTAMP=100000
__SEQNUM=1
__SEQNUM_ID=ba37c62fcacf406aad088166c4963cd1
_BOOT_ID=c220efc91dd440deb89d82c98ddb11a5
NUMBER=1
$ journalctl --file two.journal -o export
__CURSOR=s=9d15197efcc54d8ca5d6e7cce28373e2;i=1;b=686dc35e78f64b6ba126018ab6e8bae9;m=186a0;t=f4240;x=57db1c7bd38b37ed
__REALTIME_TIMESTAMP=1000000
__MONOTONIC_TIMESTAMP=100000
__SEQNUM=1
__SEQNUM_ID=9d15197efcc54d8ca5d6e7cce28373e2
_BOOT_ID=686dc35e78f64b6ba126018ab6e8bae9
NUMBER=2
__CURSOR=s=9d15197efcc54d8ca5d6e7cce28373e2;i=2;b=686dc35e78f64b6ba126018ab6e8bae9;m=30d40;t=2dc6c0;x=3513732159948113
__REALTIME_TIMESTAMP=3000000
__MONOTONIC_TIMESTAMP=200000
__SEQNUM=2
__SEQNUM_ID=9d15197efcc54d8ca5d6e7cce28373e2
_BOOT_ID=686dc35e78f64b6ba126018ab6e8bae9
NUMBER=3
The first file (one.journal) contains a single entry from the first boot
(c220efc91dd440deb89d82c98ddb11a5) and a realtime timestamp of 2s. The
second file (two.journal) contains two entries from the second boot
(686dc35e78f64b6ba126018ab6e8bae9) - the first entry has a realtime
timestamp of 1s (i.e. _earlier_ than the tail timestamp of the first
boot), and the second entry has a realtime timestamp of 3s (i.e. after
a time correction).
This works fine for forward iteration, but seeking directly to the
second entry is when things go south:
First entry:
$ python3 <<EOF
c = "s=ba37c62fcacf406aad088166c4963cd1;i=1;b=c220efc91dd440deb89d82c98ddb11a5;m=186a0;t=1e8480;x=39d4323a8e5a248d"
from systemd import journal; j = journal.Reader(path="."); j.seek_cursor(c); j.get_next(); print(j.test_cursor(c))
EOF
True
Second entry:
$ python3 <<EOF
c = "s=9d15197efcc54d8ca5d6e7cce28373e2;i=1;b=686dc35e78f64b6ba126018ab6e8bae9;m=186a0;t=f4240;x=57db1c7bd38b37ed"
from systemd import journal; j = journal.Reader(path="."); j.seek_cursor(c); j.get_next(); print(j.test_cursor(c))
EOF
False
That's because:
- we call sd_journal_seek_cursor(...) and parse it (seqnum, seqnum_id,
boot_id, realtime, ...)
- we call sd_journal_next() -> real_journal_next() which iterates over
all open journal files to find all candidate entries and pick the
best one
- for each journal file we call next_beyond_location()
- for next_beyond_location(one.journal) we get:
- last_direction is _DIRECTION_INVALID, so we call find_location_with_matches()
- we fall all the way through to journal_file_move_to_entry_by_realtime()
- here we try to find the first entry with realtime >= 1s; this file
has only one entry with realtime = 2s which matches
- we got a match and new_file is unset, so we set it to one.journal
- for next_beyond_location(two.journal) we get:
- we fall through to find_location_with_matches()
- here we have a matching seqnum, so we call journal_file_move_to_entry_by_seqnum()
- we got another match but new_file is set, so we call compare_locations(two.journal, one.journal):
- we fall through to compare_boot_ids()
- we get the tail timestamps of both journal files and return
CMP(second boot, first boot) -> CMP(3s, 2s) = 1
- this bubbles up back to real_journal_next() where found = false, so
new_file remains one.journal (the wrong journal file)
- this is followed by set_location(one.journal), which makes the
following sd_journal_test_cursor() call fail, as the
boot_id/seqnum_id/... of the current position don't match with the
cursor
To fix this, let's track the journal file that holds the exact cursor
match, and override the chosen candidate if needed once we process all
open journals.
Resolves: #31516
System and Service Manager
Details
Most documentation is available on systemd's website.
Assorted, older, general information about systemd can be found in the systemd Wiki.
Information about build requirements is provided in the README file.
Consult our NEWS file for information about what's new in the most recent systemd versions.
Please see the Code Map for information about this repository's layout and content.
Please see the Hacking guide for information on how to hack on systemd and test your modifications.
Please see our Contribution Guidelines for more information about filing GitHub Issues and posting GitHub Pull Requests.
When preparing patches for systemd, please follow our Coding Style Guidelines.
If you are looking for support, please contact our mailing list, join our IRC channel #systemd on libera.chat or Matrix channel
Stable branches with backported patches are available in the stable repo.
We have a security bug bounty program sponsored by the Sovereign Tech Fund hosted on YesWeHack
Repositories with distribution packages built from git main are available on OBS, and also repositories with packages built from the latest stable release
