CPU architecture is determined by uname(), rather than the compile time
macros like __i386__, __x86_64__, __aarch64__.
Let's conditionalize with uname_architecture().
Follow-up for d518675741.
Since the D-Bus DNS-SD API was introduced in c3036641f0ed (v236),
`RegisterService()` has created an `sd_bus_track` object to destroy the
registered service when the registering client disconnects from the bus.
However, the track object is a `_cleanup_(sd_bus_track_unrefp)` local in
`bus_method_register_service()` that is never stored anywhere, so it is
destroyed again as soon as the method handler returns. The vanish
handler (`dnssd_registered_service_on_bus_track()`) has thus been dead
code for the past ~8 years.
The observable effect: services registered via D-Bus silently persist
after the client disconnects — unowned, advertised until resolved exits,
and impossible to re-register since the identifier stays taken
(`DNSSD_SERVICE_EXISTS`). At the same time, the registration doesn't
survive a resolved restart, so the current behavior is neither reliably
ephemeral nor reliably persistent. Noticed while reviewing a project
that (unknowingly relying on this bug) registers a service with a
one-shot `gdbus call` and expects it to persist.
This PR:
1. splits the goodbye/zone-removal logic out of
`bus_dnssd_method_unregister()` into a helper — the vanish path needs
the full teardown, not just `dnssd_registered_service_free()`, which
would leave the service's RRs published in the per-link mDNS zones;
2. stores the track object in `DnssdRegisteredService`, making client
tracking work as originally intended;
3. documents the lifetime semantics in `org.freedesktop.resolve1(5)`
(matching the equivalent Avahi D-Bus API, whose entry groups are
likewise bound to the client connection) and points to `*.dnssd` files
for registrations independent of a client's lifetime;
4. adds a TEST-75 case covering the connection-bound lifetime.
Note this is a behavioral change for fire-and-forget clients (one-shot
`busctl`/`gdbus` invocations). Arguably those were never working
reliably (registrations silently vanished on any resolved restart, and
stale entries blocked re-registration), but if this is deemed too risky,
the alternative would be to drop the dead tracking code and codify the
persist-until-exit behavior instead — I'd argue tracking as originally
intended is the better contract.
Verification: code compiles; the new integration test is included.
* 60fbc2d955 Update systemd to version 261.2 / rev 473 via SR 1368021
* c32d9727e6 Update systemd to version 261.1 / rev 472 via SR 1365923
* 74fd672013 Update systemd to version 260.3 / rev 471 via SR 1362701
* 769e1c4254 Update systemd to version 260.2 / rev 470 via SR 1361320
SUSE regularly builds packages into the future to track issues like the the
ones that might happen in 2038:
https://www.reddit.com/r/linux/comments/xjtf3q/in_the_year_2038/
Make sure that os_release_support_ended() doesn't fail in this case.
PS: on 32-bit systems time_t cannot represent dates past 2038, so use a safe
date there instead.
shellcheck 0.10+ reports SC2329 ("This function is never invoked") for
the per-testcase cleanup() functions in TEST-75-RESOLVED.sh, which are
only invoked indirectly via trap. The existing SC2317 markers don't
cover it, and inserting a new testcase shuffles which definitions the
heuristic fires on, making pre-existing findings show up as new in
differential CI runs. Extend the markers to SC2317,SC2329, matching
what testcase_dot_strict_per_link_verify() in this file and other test
units already do.
Verify that a DNS-SD service registered via the D-Bus RegisterService()
call is bound to the lifetime of the client's bus connection: since
busctl disconnects right after the call returns, the service must be
unregistered again shortly after. Wait for resolved's vanish handler
log message as proof that the client tracking triggered the
unregistration, check that a subsequent UnregisterService() fails
specifically because the service is not known anymore, and verify that
the identifier can be registered again — previously it stayed taken by
the vanished client's service forever, failing with
DNSSD_SERVICE_EXISTS.
A dummy link with mDNS enabled makes sure the per-scope goodbye/zone
teardown path actually runs, and since resolved would be restarted
automatically if it crashed in it, letting the D-Bus level checks pass
vacuously, verify the InvocationID stayed the same throughout.
Since the D-Bus DNS-SD API was introduced in c3036641f0 ("resolved:
implement D-Bus API for DNS-SD", v236), RegisterService() has set up an
sd_bus_track object so that a registered service is destroyed when the
client that registered it disconnects from the bus — there's even a
handler logging "Client of active request vanished, destroying DNS-SD
service."
However, the track object was never stored anywhere: it is declared as
a _cleanup_(sd_bus_track_unrefp) local in bus_method_register_service()
and hence destroyed again when the method handler returns, taking the
tracking down with it. The vanish handler was thus dead code, and
services registered via D-Bus silently persisted after their client
disconnected — unowned, impossible to re-register (the identifier
stays taken, failing with DNSSD_SERVICE_EXISTS), and advertised until
systemd-resolved itself exited.
Store the track object in DnssdRegisteredService, pinning it for the
lifetime of the service, so that client tracking works as originally
intended. Route the vanish path through the full unregistration logic
(goodbye packets + removal from the per-link mDNS zones), since freeing
the service alone would leave its RRs published in the zones.
Note for fire-and-forget clients (e.g. one-shot busctl/gdbus calls):
these were never reliable — the registration silently vanished on any
resolved restart — and now behave as documented for the corresponding
Avahi API as well: the bus connection must be kept open for as long as
the service is to remain registered. Document that in the man page and
NEWS, and point to *.dnssd files for registrations independent of a
client's lifetime.
dns_scope_announce(scope, /* goodbye= */ true) is not scoped to a
specific service: it walks the whole per-link mDNS zone and flags every
established RR as goodbye (TTL 0). Unregistering a single DNS-SD
service via the UnregisterService() D-Bus call hence withdrew the
host's own A/AAAA records and every other published service on the
link from remote caches, with nothing re-announcing them afterwards.
Introduce dns_scope_send_goodbye(), which sends an unsolicited response
for a caller-provided RR set, and let the DNS-SD unregistration path
send goodbye only for the RRs of the service actually being removed,
mirroring the flag logic of dns_scope_announce().
The goodbye answer is built per scope, and only includes records
actually established in the respective zone (or under re-verification,
in which case they are still served from it): a per-service goodbye
must take care not to withdraw records that are still being probed for
uniqueness, were withdrawn again after a conflict (in which case the
name belongs to the neighbor), or were never published in the scope in
the first place (e.g. on MulticastDNS=resolve links, which have an mDNS
scope but do not publish DNS-SD services).
This becomes more important with the next commit, which promotes
unregistration from an explicit method call to an automatic consequence
of the registering client disconnecting from the bus.
systemd-importd runs qcow2_detect()/qcow2_convert() over images it has just
downloaded (see pull-raw.c), so the header, the L1/L2 tables and every offset
and cluster descriptor in them are attacker controlled. There is a manual test
for the happy path, but nothing that feeds it a malformed image.
The corpus is two images, a plain data cluster and a zlib-compressed one (i.e.
decompress_zlib_raw()), at the 512 byte minimum cluster size and trimmed to the
last byte the converter reads, so 2K each. A zero cluster and a QCOW3 header are
a flipped descriptor bit and a bumped version field away from these, so they are
left to the fuzzer rather than carried in the tree.
fuzz-compress only ever feeds the decompressors their own output: it compresses
the fuzzer input and then decompresses the result, so the stream the decoders
see is well formed by construction. The blobs they get in reality come out of
journal files, systemd-journal-remote streams, coredumps and downloaded images,
i.e. from wherever the data was written, which is not necessarily somewhere we
trust.
So hand them the fuzzer input directly instead, across all four entrypoints:
decompress_blob(), decompress_startswith(), decompress_stream(), and the
decompressor_detect()/decompressor_push() pair that systemd-importd feeds from
the network. The latter is pushed in chunks, so that the decoders see the same
fragmentation a real download produces. The decompressed size is capped, as an
unbounded decode of a hostile blob measures the allocator rather than the
decoder.
The corpus carries the two truncated stream headers that used to send
decompress_startswith() allocating until OOM, fixed in the previous commit.
decompress_startswith() decodes just enough of a blob to compare its leading
bytes against a prefix, and grows the output buffer whenever a round of
decoding came back with less than prefix_len + 1 bytes decoded. That growth is
only the right answer while the decoder actually wants more output room. If it
instead ran out of input, i.e. the stream is truncated, then no amount of extra
room will help, and the gzip and bzip2 paths double their allocation on every
iteration until the process is out of memory.
Neither decoder lets us tell the two cases apart by return code alone:
inflate() reports the stalled stream as Z_BUF_ERROR, which we cannot simply
refuse since that is also how it asks for a bigger output buffer, and
BZ2_bzDecompress() does not distinguish them at all, it just keeps saying
BZ_OK. The output space left over does tell us, though: a decoder that stopped
without filling the buffer we gave it is waiting for input that is not coming,
so report the stream as corrupt instead of growing.
A 10 byte gzip header, or a 4 byte bzip2 header, with nothing behind it was
enough to reach a single 4 GiB allocation this way. The xz and zstd paths
already terminate, the former because liblzma turns a stalled stream into
LZMA_BUF_ERROR.
Note that no caller can reach this today: the only non-test caller is
decompress_startswith_journal(), and compression_supported_journal() admits
only NONE/XZ/LZ4/ZSTD. It is a trap for whoever wires up the other two, so
let's close it now.
The comment was wrong. The average word is about 6 characters, so in
64 iterations, the probability of never landing on a word boundary is
~(1 - 1/7)**64, about 0.01%.
hostname_pick_word() used to seek around in the wordlist file and read
individual lines with read_line()/read_stripped_line(). Instead, slurp the
whole file into memory use offset arithmetic. The wordlists are small, so
reading them entirely is faster and there is really no reason to do the
complicated thing. The new code is 15% faster (the slow parts are the
hashing and chaseat, they actually dominate over the reading of the data).
The "raced with truncation" case is gone, since the buffer is a consistent
snapshot of the file.
The random offsets derived from the machine ID are unchanged, so the
picked words stay the same.
112cfb1814 tried to improve handling of
paths when root is specified. It said:
> To avoid answering what a relative path means when called with a root
> prefix path_strv_canonicalize is now path_strv_canonicalize_absolute
> and only considers absolute paths. Fortunately all users of already call
> path_strv_canonicalize with a list of absolute paths.
Of course, other users were added later and for example now
SYSTEMD_HOSTNAME_WORDLIST_PATH=build/hostname-wordlist/ ... doesn't
work as expected. If root is not specified, there is really no reason
to ignore relative paths. They work relative to CWD per the usual convention.
It's up to the user to not use relative paths in a context where they
should not be used. When root is specified, continue to ignore the relative
paths, since there is no clear interpreation what they mean, but emit
a debug message.
This is useful to check if how fast the code is (painfully slow) and whether
worlds indeed are selected with a flat distribution probability (they are).
The code is slow because the hashing and because chaseat is exteremely
inefficient :(
Move the logic that sends goodbye messages, removes a registered DNS-SD
service's RRs from all mDNS zones and frees the service out of
bus_dnssd_method_unregister() into a new helper. While at it, add the
nowadays customary parameter comments and ", ignoring" log message
suffix to the moved code. No functional change.
This is preparation for unregistering services when the D-Bus client
that registered them disconnects: that path needs the full teardown as
well, not just dnssd_registered_service_free(), which would leave the
service's RRs published in the per-link mDNS zones.
When networkd starts or restarts, links are enumerated in ifindex
order. A slave enumerated before its master cannot register itself in
link_append_to_master(), as the master Link object does not exist yet.
The master then wrongly believes it has no slaves until the next
RTM_NEWLINK message for the slave arrives.
The stale slaves set affects operational state propagation
(degraded-carrier detection) and any code that inspects the slaves of
a master.
For instance, on restart, when networkd thinks that the master has no
slaves yet, we try to set the bond fail-over policy, which the kernel
then refuses because in reality the bond does have slaves.
To fix the issue, this updates the list of slaves after enumeration.
Co-authored-by: Yu Watanabe <watanabe.yu+github@gmail.com>
Currently `RestrictFileSystemAccess` isn't trying hard enough to enforce
W^X and thus there are still places where an executable and writable
mapping can be created:
* `/proc/<pid>/mem` `FOLL_FORCE` writes
* `PTRACE_POKE{TEXT,DATA}` `FOLL_FORCE` writes
* `MAP_PRIVATE | MAP_EXECUTABLE` memory mappings
Let's block all of these mechanisms. Do note that
`RestrictFileSystemAccess` is still in very active development and also
cannot meaningfully be used on general purpose distros and systems.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Describe the system-wide W^X rule and the new proc_mem.force_override=never
and seccomp prerequisites, and drop the advice to block writable and
executable mappings by other means, which the policy now does itself.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
The kernel silently ignores command line options it doesn't know. So a
kernel without CONFIG_PROC_MEM_* would pass the command line check while
still allowing FOLL_FORCE memory overwrites. Write one byte through
/proc/self/mem into a private read-only anonymous page. That can only
succeed with FOLL_FORCE writes supported.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
In addition to PTRACE_POKE{TEXT,DATA} /proc/<pid>/mem can also be used
to FOLL_FORCE write into a trusted process's executable pages. Require
proc_mem.force_override=never.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
PTRACE_POKETEXT and PTRACE_POKEDATA use FOLL_FORCE to forcibly write
into a tracee's memory. That includes executable read-only pages. If we
restrict execution to trusted binaries PID 1 must enforce that it's
impossible to abuse this to get untrusted code execution. Note, that
usermodehelpers are not subject to PID 1's seccomp filters.
Install a ptrace seccomp filter from PID 1 if RestrictFileSystemAccess=
is requested. Every process it spawns will inherit it.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
PTRACE_POKETEXT and PTRACE_POKEDATA use FOLL_FORCE to forcibly write
into a tracee's memory. That includes executable read-only pages. If we
restrict execution to trusted binaries PID 1 must enforce that it's
impossible to abuse this to get untrusted code execution. Note, that
usermodehelpers are not subject to PID 1's seccomp filters.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Right now RestrictFileSystemAccess= checks that a file comes from a
trusted dm-verity device, but it doesn't guard against mapping that file
and making use of COW modifications. So a MAP_PRIVATE mapping of a
trusted binary can be made writable, modified, and executed, while the
vma still points to the trusted file and the pages are
attacker-controlled anonymous copies.
Enforce W^X: deny any mapping that is writable and executable at once,
and refuse to make a private file mapping executable once it has been
modified through copy-on-write.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Make sure the tests can tell the difference between an actual denial and
a probe that simply cannot be run.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
If dbus dies, it is then immediately reactivated by dbus.socket as soon
as a client reconnects. In that case pid1's own connection to the bus
drops and is reestablished by manager_recheck_dbus() (which comes from
the unit_notify() when dbus.services goes back to being running).
As part of bringing the bus back up, bus_setup_api() issues
GetNameOwner() for every name we watch on behalf of a BusName= unit.
For a Type=dbus service this is a problem. Consider this scenario:
1. spearmint.service is Type=dbus, owns com.example.Spearmint, and is
running.
2. The broker crashes and is re-activated through dbus.socket.
3. spearmint's client connection dropped too, so it is now reconnecting
to the new broker to grab its name again.
4. pid1 racily reconnects first given it is triggered the moment
dbus.service reaches SERVICE_RUNNING, whereas spearmint is an
ordinary client that must first notice its own disconnection and thus
when we do GetNameOwner() com.example.Spearmint is reported as having
no owner.
5. service_bus_name_owner_change() sets bus_name_good = false, and since
service_good() returns false for a Type=dbus service without its
name, service_enter_running() stops the (perfectly healthy, other
than dbus kicking the bucket temporarily) service.
In other words, a broker restart can tear down running Type=dbus
services that did nothing wrong, really.
So, to fix this, we can use the fact that a NameOwnerChanged signal is
definitive. That is, the name's owner changed while we were connected,
so a service that crashes or drops its name can still be stopped
immediately. By contrast a GetNameOwner() reply is less authoritative.
There is, importantly, no change to how losing the name on a live
connection works, we only give grace on reconnect.