1
0
mirror of https://github.com/systemd/systemd synced 2026-03-30 03:34:49 +02:00

Compare commits

...

13 Commits

Author SHA1 Message Date
Yu Watanabe
3f91ffe0fe
core: Verify inherited FDs are writable for stdout/stderr (#39674)
When inheriting file descriptors for stdout/stderr (either from stdin or
when making stderr inherit from stdout), we previously just assumed they
would be writable and dup'd them. This could lead to broken setups if
the inherited FD was actually opened read-only.

Before dup'ing any inherited FDs to stdout/stderr, verify they are
actually writable using the new fd_is_writable() helper. If not, fall
back to /dev/null (or reopen the terminal in the TTY case) with a
warning, rather than silently creating a broken setup where output
operations would fail.
2025-11-20 08:19:46 +09:00
Quentin Deslandes
5b0e262f45 network: clear existing routes if Gateway= is empty in [Network]
Add support for an empty Gateway= in [Network] to clear the existing
routes. This change will allow users to remove the default route from a
drop-in file.
2025-11-20 07:16:26 +09:00
Zbigniew Jędrzejewski-Szmek
9d7a70003d man: add 'testing' as one of the suggestions for DEPLOYMENT=
Looking at the list, "test" or "testing" seems to be a fairly generic entry
that is missing from the list of suggestions. I went with "testing" because it
fits better with the other item, e.g. "staging".

In https://github.com/systemd/systemd/issues/38743 "laboratory" was also
suggested. I didn't include this because that is more about the location, not
deployment type. Any of the other deployments could be in a "laboratory".

Closes https://github.com/systemd/systemd/issues/38743.
2025-11-19 22:01:15 +01:00
Chris Down
0b7dfc036d
tests: ASSERT_SIGNAL: Prevent hallucinating parent as child and confusing exit codes with signals (#39807)
This series fixes two distinct, pretty bad bugs in `ASSERT_SIGNAL`.
These bugs can allow failing tests to pass, and can also cause the test
runner to silently terminate prematurely in a way that looks like
success.

This is not theoretical, see
https://github.com/systemd/systemd/pull/39674#discussion_r2540552699 for
a real case of this happening.

---

Bug 1: Parent process hallucinates it is the child and re-executes the
expression being tested

Previously, assert_signal_internal() returned 0 in two mutually
exclusive states:

1. We are the child process (immediately after fork()).
2. We are the parent process, and the child exited normally (status 0).

The macro failed to distinguish these cases. If a child failed to crash
as expected, the parent received 0, incorrectly interpreted it as it
being the child, and re-executed the test expression inside the parent
process.

This can cause tests to falsely pass. The parent would successfully run
the expression (which wasn't supposed to crash in the parent), succeed,
and call _exit(EXIT_SUCCESS).

The second consequence is silent truncation. When the parent called
_exit(), it terminated the entire test runner immediately. Any
subsequent tests in the same binary were never executed.

---

Bug 2: Conflation of exit codes and signals

The harness returned the raw si_status without checking si_code. This
meant that an exit code was indistinguishable from a signal number. For
example, if a child process failed and called exit(6), the harness
reported it as having been killed by SIGABRT (signal 6).

---

This PR both fixes the bugs and reworks the ASSERT_SIGNAL infrastructure
to ensure this is very unlikely to regress:

- assert_signal_internal now returns an explicit control flow enum
(FORK_CHILD / FORK_PARENT) separate from the status data. This makes it
structurally impossible for the parent to hallucinate that it is the
child.
- The output parameter is only populated with a signal number if si_code
confirms the process was killed by a signal. Normal exits return 0.
2025-11-20 03:52:02 +08:00
Luca Boccassi
6d36d07599 docs: elf metadata specs have moved to uapi-group 2025-11-19 20:00:25 +01:00
Chris Down
e21a431ec4 tests: ASSERT_SIGNAL: Do not allow parent to hallucinate it is the child
assert_signal_internal() returns 0 in two distinct cases:

1. In the child process (immediately after fork returns 0).
2. In the parent process, if the child exited normally (no signal).

ASSERT_SIGNAL fails to distinguish these cases. When a child exited
normally (case 2), the parent process receives 0, incorrectly interprets
it as meaning it is the child, and re-executes the test expression
inside the parent process. Goodness gracious!

This causes two severe test integrity issues:

1. False positives. The parent can run the expression, succeed, and call
   _exit(EXIT_SUCCESS), causing the test to pass even though no signal
   was raised.
2. Silent truncation. The _exit() call in the parent terminates the test
   runner prematurely, preventing subsequent tests in the same file from
   running.

Example of the bug in action, from #39674:

    ASSERT_SIGNAL(fd_is_writable(closed_fd), SIGABRT)

This test should fail (fd_is_writable does not SIGABRT here), but with
the bug, the parent hallucinated being the child, re-ran the expression
successfully, and exited with success.

Fix this by refactoring assert_signal_internal() to be much more strict
about separating control flow from data.

The signal status is now returned via a strictly typed output parameter,
guaranteeing that determining whether we are the child is never
conflated with whether the child exited cleanly.
2025-11-20 02:40:07 +08:00
Chris Down
d759ed527c tests: ASSERT_SIGNAL: Ensure sanitisers do not mask expected signals
ASAN installs signal handlers to catch crashes like SIGSEGV or SIGILL.
When these signals are raised, ASAN traps them, prints an error report,
and then typically terminates the process with a different signal (often
SIGABRT) or a non-zero exit code.

This interferes with ASSERT_SIGNAL when checking for specific crash
signals (for example, checking that a function raises SIGSEGV). In such
a case, the test harness sees the ASAN termination signal rather than
the expected signal, causing the test to fail.

Fix this by resetting the signal handler to SIG_DFL in the child process
immediately before executing the test expression. This ensures the
kernel kills the process directly with the expected signal, bypassing
ASAN's interceptors.
2025-11-20 02:40:07 +08:00
Chris Down
39adecfcd8 tests: ASSERT_SIGNAL: Stop exit codes from masquerading as signals
When a child process exits normally (si_code == CLD_EXITED),
siginfo.si_status contains the exit code. When it is killed by a signal
(si_code == CLD_KILLED or CLD_DUMPED), si_status contains the signal
number.  However, assert_signal_internal() returns si_status blindly.
This causes exit codes to be misinterpreted as signal numbers.

This allows failing tests to silently pass if their exit code
numerically coincides with the expected signal. For example, a test
expecting SIGABRT (6) would incorrectly pass if the child simply exited
with status 6 instead of being killed by a signal.

Fix this by checking si_code. Only return si_status as a signal number
if the child was actually killed by a signal (CLD_KILLED or CLD_DUMPED).
If the child exited normally (CLD_EXITED), return 0 to indicate that no
signal occurred.
2025-11-20 02:40:07 +08:00
Chris Down
171ceb4a00 core: Verify inherited FDs are writable for stdout/stderr
When inheriting file descriptors for stdout/stderr (either from stdin
or when making stderr inherit from stdout), we previously just assumed
they would be writable and dup'd them. This could lead to broken setups
if the inherited FD was actually opened read-only.

Before dup'ing any inherited FDs to stdout/stderr, verify they are
actually writable using the new fd_is_writable() helper. If not, fall
back to /dev/null (or reopen the terminal in the TTY case) with a
warning, rather than silently creating a broken setup where output
operations would fail.
2025-11-20 02:02:21 +08:00
Chris Down
592c57e586 fd-util: Add fd_is_writable() to check if FD is opened for writing
This checks whether a file descriptor is valid and opened in a mode that
allows writing (O_WRONLY or O_RDWR). This is useful when we want to
verify that inherited FDs can actually be used for output operations
before dup'ing them.

The helper explicitly handles O_PATH file descriptors, which cannot be
used for I/O operations and thus are never writable.
2025-11-20 02:02:21 +08:00
Yu Watanabe
f7df0eab8d core/socket: do not log failure in setting socket option with number
This also downgrade to the debug level when the option is simply not
supported.

Follow-up for b81a14b91efea17631d634f5dbd69314780815ab.
Fixes #39792.
2025-11-19 18:16:31 +01:00
Zbigniew Jędrzejewski-Szmek
8d50438ba5 test/TEST-74-AUX-UTILS: fix racy check
We were getting a list of invocation IDs, picking one at random,
and then querying the unit. This is obviously racy.

TEST-74-AUX-UTILS.sh[2873]: + varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List
                              '{"invocationID": "2052c9a5-7983-4f72-9910-c49e38c91dab"}'
TEST-74-AUX-UTILS.sh[3707]: Method call io.systemd.Unit.List() failed: io.systemd.Unit.NoSuchUnit

The complicated varlink + jq callout is replaced by a simple systemctl call.
I think that's better to avoid a complicated jq expression.

Fixes https://github.com/systemd/systemd/issues/38647.
2025-11-19 17:09:02 +00:00
Chris Down
408e8d361f tests: Avoid variable shadowing in ASSERT_SIGNAL
The ASSERT_SIGNAL macro uses a fixed variable name, `_r`. This prevents
nesting the macro (like ASSERT_SIGNAL(ASSERT_SIGNAL(...))), as the inner
instance would shadow the outer instance's variable.

Switch to using the UNIQ_T helper to generate unique variable names at
each expansion level. This allows the macro to be used recursively,
which is required for upcoming regression tests regarding signal
handling logic.
2025-11-19 20:31:57 +08:00
16 changed files with 264 additions and 461 deletions

View File

@ -1,176 +1 @@
--- [This content has moved to the UAPI group website](https://uapi-group.org/specifications/specs/elf_dlopen_metadata/)
title: Dlopen Metadata for ELF Files
category: Interfaces
layout: default
SPDX-License-Identifier: LGPL-2.1-or-later
---
# `dlopen()` Metadata for ELF Files
*Intended audience: hackers working on packaging ELF files that use dlopen to load libraries.*
## Motivation
Using `dlopen()` to load optional dependencies brings several advantages: programs can gracefully downgrade
a feature when a library is not available, and the shared library is only loaded into the process (and its
ELF constructors are run) only when the requested feature is actually used. But it also has some drawbacks,
and the main one is that it is harder to track a program's dependencies, since unlike build-time dynamic
linking there will not be a mention in the ELF metadata. This specification aims to solve this problem by
providing a standardized specification for a custom ELF note that can be used to list `dlopen()`
dependencies.
## Implementation
This document will attempt to define a common metadata format specification, so that multiple implementers
might use it when coding upstream software, and packagers might use it when building packages and setting
dependencies.
The metadata will be embedded in a series of new, 4-byte-aligned, allocated, 0-padded, read-only ELF header
sections, in a JSON array containing name-value objects, either one ELF note per dependency or as a single
note listing multiple dependencies in the top-level array. Implementers working on parsing ELF files should
not assume a specific list of names, but parse anything that is included in the section, and should look for
the note using the `note type`. Implementers working on build tools should strive to use the same names, for
consistency. The most common will be listed here.
* Section header
```
SECTION: `.note.dlopen`
note type: `0x407c0c0a`
Owner: `FDO` (FreeDesktop.org)
Value: an array of JSON objects encoded as a zero-terminated UTF-8 string
```
* JSON payload
```json
[
{
"soname": ["libfoo.so.1"],
"feature": "foo",
"description": "Enables the foo feature",
"priority": "recommended"
}
]
```
The format is a single JSON array containing objects, encoded as a zero-terminated `UTF-8` string. Each key
in each object shall be unique as per recommendations of [RFC8259](https://datatracker.ietf.org/doc/html/rfc8259#section-4).
Strings shall not contain any control characters or use `\uXXX` escaping.
Reference implementations of [packaging tools for `.deb` and `.rpm`](https://github.com/systemd/package-notes)
are available, and provide macros/helpers to parse the note when building packages and adding dependencies.
## Well-known keys
The metadata format is intentionally extensible, so that upstreams and later revisions of this spec can add
their own information. The 'soname' array is required, with at least one element, everything else is
optional. If alternative soname versions for the same library are supported at the same time, an array can
be used, listing the most preferred first, and parsers are expected to select only the first one that is
available on the system, as it is a mechanism to specify alternatives. If the `priority` field is used, it
must follow the specification and use one of the values specified in the table. If it is not specified, a
parser should assume 'recommended' if a priority is needed. If the `feature` field is used, it will identify
an individual feature, and multiple entries using the same `feature` denote functionality that requires all
of the libraries they specify in order to be enabled.
| Key name | Key type | Mandatory | Key description | Example value |
|-------------|----------------------------|-----------|--------------------------------------------------------------------------|----------------------------------|
| soname | array of strings | yes | The library names loaded by `dlopen()` | [ "libfoo.so.1", "libfoo.so.0" ] |
| feature | string | no | A keyword identifying the feature that the library contributes to enable | "foo" |
| description | string | no | A human-readable text string describing the feature | "Enables the foo feature" |
| priority | string | no | The priority of the feature, one of: required, recommended, suggested | "recommended" |
### Priority definition
| Priority | Semantics |
|-------------|--------------------------------------------------------------------------------------------------------------------------------------|
| required | Core functionality needs the dependency, the binary will not work if it cannot be found |
| recommended | Important functionality needs the dependency, the binary will work but in most cases the dependency should be provided |
| suggested | Secondary functionality needs the dependency, the binary will work and the dependency is only needed for full-featured installations |
### Displaying `dlopen()` notes
The raw ELF section can be extracted using `objdump`:
```console
$ objdump -j .note.dlopen -s /usr/lib64/systemd/libsystemd-shared-257.so
/usr/lib64/systemd/libsystemd-shared-257.so: file format elf64-x86-64
Contents of section .note.dlopen:
0334 04000000 8e000000 0a0c7c40 46444f00 ..........|@FDO.
0344 5b7b2266 65617475 7265223a 22627066 [{"feature":"bpf
0354 222c2264 65736372 69707469 6f6e223a ","description":
0364 22537570 706f7274 20666972 6577616c "Support firewal
0374 6c696e67 20616e64 2073616e 64626f78 ling and sandbox
0384 696e6720 77697468 20425046 222c2270 ing with BPF","p
0394 72696f72 69747922 3a227375 67676573 riority":"sugges
03a4 74656422 2c22736f 6e616d65 223a5b22 ted","soname":["
03b4 6c696262 70662e73 6f2e3122 2c226c69 libbpf.so.1","li
03c4 62627066 2e736f2e 30225d7d 5d000000 bbpf.so.0"]}]...
03d4 04000000 9e000000 0a0c7c40 46444f00 ..........|@FDO.
...
```
It is more convenient to use a higher level tool:
```console
$ dlopen-notes /usr/lib64/systemd/libsystemd-shared-257.so
# /usr/lib64/systemd/libsystemd-shared-257.so
[
{
"feature": "archive",
"description": "Support for decompressing archive files",
"priority": "suggested",
"soname": [
"libarchive.so.13"
]
},
{
"feature": "bpf",
"description": "Support firewalling and sandboxing with BPF",
"priority": "suggested",
"soname": [
"libbpf.so.1",
"libbpf.so.0"
]
},
...
```
`dlopen-notes` can display the notes grouped in a few different ways.
One option is to filter the libraries by "feature". This answers the
question "what libraries are needed to provide specified features":
```console
$ dlopen-notes.py -f archive,bpf /usr/lib64/systemd/libsystemd-shared-257.so
# grouped by feature
{
"bpf": {
"description": "Support firewalling and sandboxing with BPF",
"sonames": {
"libbpf.so.1": "suggested",
"libbpf.so.0": "suggested"
}
},
"archive": {
"description": "Support for decompressing archive files",
"sonames": {
"libarchive.so.13": "suggested"
}
}
}
The format that is used when building `deb` packages:
```console
$ dlopen-notes -s /usr/lib64/systemd/libsystemd-shared-257.so
libarchive.so.13 suggested
libbpf.so.0 suggested
libbpf.so.1 suggested
...
```
The format that can be useful when building `rpm` packages:
```console
$ dlopen-notes --rpm-requires archive --rpm-recommends bpf /usr/lib64/systemd/libsystemd-shared-257.so
Requires: libarchive.so.13()(64bit)
Recommends: libbpf.so.1()(64bit)
```

View File

@ -1,236 +1 @@
--- [This content has moved to the UAPI group website](https://uapi-group.org/specifications/specs/package_metadata_for_executable_files/)
title: Package Metadata for Executable Files
category: Interfaces
layout: default
SPDX-License-Identifier: LGPL-2.1-or-later
---
# Package Metadata for Executable Files
*Intended audience: hackers working on userspace subsystems that
create or manipulate ELF or PE/COFF binaries
or parse core files.*
## Motivation
ELF binaries get stamped with a unique, build-time generated hex string identifier called `build-id`,
[which gets embedded as an ELF note called `.note.gnu.build-id`](https://fedoraproject.org/wiki/Releases/FeatureBuildId).
In most cases, this allows a stripped binary to be associated with its debugging information.
It is used, for example, to dynamically fetch DWARF symbols from a debuginfo server, or
to query the local package manager and find out the package metadata or, again, the DWARF
symbols or program sources.
However, this usage of the `build-id` requires either local metadata, usually set up by
the package manager, or access to a remote server over the network. Both of those might
be unavailable or forbidden.
Thus it becomes desirable to add additional metadata to a binary at build time, so that
`systemd-coredump` and other services analyzing core files are able to extract said
metadata simply from the core file itself, without external dependencies.
This metadata is stored as a section in the executable file,
so that it will be loaded into memory along with the text and data of the binary,
and will be preserved in a core dump.
This metadata can also be easily read from the file on disk,
so it can be used to identify provenience of files,
independently of any package management system,
even if the file is renamed or copied.
## Implementation
This document will attempt to define a common metadata format specification, so that
multiple implementers might use it when building packages, or core file analyzers, and
so on.
Implementers working on parsing the metadata should not assume a specific list of names,
but parse anything that is included in the JSON object.
Implementers working on build tools should strive to use the same names, for consistency.
The most common will be listed here.
When corresponding to the content of os-release, the values should match, again for consistency.
If available, the metadata should also include the debuginfod server URL that can provide
the original executable, debuginfo and sources, to further facilitate debugging.
### ELF header section
The metadata will be embedded in a single, 4 byte-aligned, allocated, NUL-padded,
read-only ELF header section, in a name-value JSON object format.
The JSON string is terminated with a NUL
and subsequently padded with NULs to a multiple of four bytes.
The `note type` must be set during creation and checked when reading.
Section: `.note.package`<br/>
`note type`: `0xcafe1a7e`<br/>
Owner: `FDO` (FreeDesktop.org)<br/>
Value: a single JSON object encoded as a NUL-terminated UTF-8 string
### PE/COFF section
The metadata will be embedded in a single, allocated, NUL-padded,
read-only COFF data section,
in a name-value JSON object format.
The JSON string is terminated with a NUL
and subsequently padded with NULs if appropriate.
The `IMAGE_SCN_CNT_INITIALIZED_DATA` section flag shall be set.
The alignment and padding shall be chosen as appropriate for the use of the PE/COFF file.
Section: `.pkgnote`<br/>
Value: a single JSON object encoded as a NUL-terminated UTF-8 string
### JSON payload
```json
{
"type":"rpm", # this provides a namespace for the package+package-version fields
"os":"fedora",
"osVersion":"33",
"name":"coreutils",
"version":"4711.0815.fc13",
"architecture":"arm32",
"osCpe": "cpe:2.3:o:fedoraproject:fedora:33", # A CPE name for the operating system, `CPE_NAME` from os-release is a good default
"appCpe": "cpe:2.3:a:gnu:coreutils:5.0", # A CPE name for the upstream application, use NVD CPE search
"debugInfoUrl": "https://debuginfod.fedoraproject.org/"
}
```
The format is a single JSON object,
encoded as a NUL-terminated `UTF-8` string.
Each name in the object shall be unique as per recommendations of
[RFC8259](https://datatracker.ietf.org/doc/html/rfc8259#section-4).
Strings shall not contain any control characters or use `\uXXX` escaping.
When it comes to JSON numbers, this specification assumes that JSON parsers
processing this information are capable of reproducing the full signed 53bit
integer range (i.e. -2⁵³+1…+2⁵³-1) as well as the full 64-bit IEEE floating
point number range losslessly (with the exception of NaN/-inf/+inf, since JSON
cannot encode that), as per recommendations of
[RFC8259](https://datatracker.ietf.org/doc/html/rfc8259#page-8). Fields in
these JSON objects are thus permitted to encode numeric values from these
ranges as JSON numbers, and should not use numeric values not covered by these
types and ranges.
If available, the metadata should also include the debuginfod server URL that can provide
the original executable, debuginfo and sources, to further facilitate debugging.
Reference implementations of [packaging tools for .deb and .rpm](https://github.com/systemd/package-notes)
are available, and provide macros/helpers to include the note in binaries built
by the package build system.
They make use of the new `--package-metadata=` flag that is available in the
`bfd`, `gold`, `mold`, and `lld` linkers
(versions 2.39, 2.39, 1.3.0, and 15.0 respectively).
This linker flag takes the JSON payload as parameter.
## Well-known keys
The metadata format is intentionally left open, so that vendors can add their own information.
A set of well-known keys is defined here, and hopefully shared among all vendors.
| Key name | Key description | Example value |
|--------------|--------------------------------------------------------------------------|---------------------------------------|
| type | The packaging type | rpm |
| os | The OS name, typically corresponding to ID in os-release | fedora |
| osVersion | The OS version, typically corresponding to VERSION_ID in os-release | 33 |
| name | The source package name | coreutils |
| version | The source package version | 4711.0815.fc13 |
| architecture | The binary package architecture | arm32 |
| osCpe | A CPE name for the OS, typically corresponding to CPE_NAME in os-release | cpe:2.3:o:fedoraproject:fedora:33 |
| appCpe | A CPE name for the upstream Application, as found in [NVD CPE search] | cpe:2.3:a:gnu:coreutils:5.0 |
| debugInfoUrl | The debuginfod server url, if available | https://debuginfod.fedoraproject.org/ |
[NVD CPE search]: https://nvd.nist.gov/products/cpe/search
### Displaying package notes
The raw ELF section can be extracted using `objdump`:
```console
$ objdump -j .note.package -s /usr/bin/ls
/usr/bin/ls: file format elf64-x86-64
Contents of section .note.package:
03cc 04000000 7c000000 7e1afeca 46444f00 ....|...~...FDO.
03dc 7b227479 7065223a 2272706d 222c226e {"type":"rpm","n
03ec 616d6522 3a22636f 72657574 696c7322 ame":"coreutils"
03fc 2c227665 7273696f 6e223a22 392e342d ,"version":"9.4-
040c 372e6663 3430222c 22617263 68697465 7.fc40","archite
041c 63747572 65223a22 7838365f 3634222c cture":"x86_64",
042c 226f7343 7065223a 22637065 3a2f6f3a "osCpe":"cpe:/o:
043c 6665646f 72617072 6f6a6563 743a6665 fedoraproject:fe
044c 646f7261 3a343022 7d000000 dora:40"}...
```
It is more convenient to use a higher level tool:
```console
$ readelf --notes /usr/bin/ls
...
Displaying notes found in: .note.gnu.build-id
Owner Data size Description
GNU 0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring)
Build ID: 40e5a1570a9d97fc48f5c61cfb7690fec0f872b2
Displaying notes found in: .note.ABI-tag
Owner Data size Description
GNU 0x00000010 NT_GNU_ABI_TAG (ABI version tag)
OS: Linux, ABI: 3.2.0
Displaying notes found in: .note.package
Owner Data size Description
FDO 0x0000007c FDO_PACKAGING_METADATA
Packaging Metadata: {"type":"rpm","name":"coreutils","version":"9.4-7.fc40","architecture":"x86_64","osCpe":"cpe:/o:fedoraproject:fedora:40"}
...
$ systemd-analyze inspect-elf /usr/bin/ls
path: /usr/bin/ls
elfType: executable
elfArchitecture: AMD x86-64
type: rpm
name: coreutils
version: 9.4-7.fc40
architecture: x86_64
osCpe: cpe:/o:fedoraproject:fedora:40
buildId: 40e5a1570a9d97fc48f5c61cfb7690fec0f872b2
```
If the binary crashes, `systemd-coredump` will display the combined information
from the crashing binary and any shared libraries it links to:
```console
$ coredumpctl info
PID: 3987823 (ls)
Signal: 11 (SEGV)
Command Line: ls --color=tty -lR /
Executable: /usr/bin/ls
...
Storage: /var/lib/systemd/coredump/core.ls.1000.88dea1b9831c420dbb398f9d2ad9b41e.3987823.1726230641000000.zst (present)
Size on Disk: 194.4K
Package: coreutils/9.4-7.fc40
build-id: 40e5a1570a9d97fc48f5c61cfb7690fec0f872b2
Message: Process 3987823 (ls) of user 1000 dumped core.
Module /usr/bin/ls from rpm coreutils-9.4-7.fc40.x86_64
Module libz.so.1 from rpm zlib-ng-2.1.7-1.fc40.x86_64
Module libcrypto.so.3 from rpm openssl-3.2.2-3.fc40.x86_64
Module libmount.so.1 from rpm util-linux-2.40.1-1.fc40.x86_64
Module libcrypt.so.2 from rpm libxcrypt-4.4.36-5.fc40.x86_64
Module libblkid.so.1 from rpm util-linux-2.40.1-1.fc40.x86_64
Module libnss_sss.so.2 from rpm sssd-2.9.5-1.fc40.x86_64
Module libpcre2-8.so.0 from rpm pcre2-10.44-1.fc40.x86_64
Module libcap.so.2 from rpm libcap-2.69-8.fc40.x86_64
Module libselinux.so.1 from rpm libselinux-3.6-4.fc40.x86_64
Stack trace of thread 3987823:
#0 0x00007f19331c3f7e lgetxattr (libc.so.6 + 0x116f7e)
#1 0x00007f19332be4c0 lgetfilecon_raw (libselinux.so.1 + 0x134c0)
#2 0x00007f19332c3bd9 lgetfilecon (libselinux.so.1 + 0x18bd9)
#3 0x000056038273ad55 gobble_file.constprop.0 (/usr/bin/ls + 0x17d55)
#4 0x0000560382733c55 print_dir (/usr/bin/ls + 0x10c55)
#5 0x0000560382727c35 main (/usr/bin/ls + 0x4c35)
#6 0x00007f19330d7088 __libc_start_call_main (libc.so.6 + 0x2a088)
#7 0x00007f19330d714b __libc_start_main@@GLIBC_2.34 (libc.so.6 + 0x2a14b)
#8 0x0000560382728f15 _start (/usr/bin/ls + 0x5f15)
ELF object binary architecture: AMD x86-64
```
(This is just a simulation. `ls` is not prone to crashing with a segmentation violation.)

View File

@ -120,6 +120,7 @@
<literal>development</literal>, <literal>development</literal>,
<literal>integration</literal>, <literal>integration</literal>,
<literal>staging</literal>, <literal>staging</literal>,
<literal>testing</literal>,
<literal>production</literal>. <literal>production</literal>.
</para> </para>

View File

@ -747,6 +747,9 @@ DuplicateAddressDetection=none</programlisting></para>
This is a short-hand for a [Route] section only containing a <varname>Gateway=</varname> key. This is a short-hand for a [Route] section only containing a <varname>Gateway=</varname> key.
This option may be specified more than once.</para> This option may be specified more than once.</para>
<para>If an empty string is specified, then the all previous assignments in both [Network] and
[Route] sections are cleared.</para>
<xi:include href="version-info.xml" xpointer="v211"/> <xi:include href="version-info.xml" xpointer="v211"/>
</listitem> </listitem>
</varlistentry> </varlistentry>

View File

@ -998,6 +998,21 @@ int fd_vet_accmode(int fd, int mode) {
return -EPROTOTYPE; return -EPROTOTYPE;
} }
int fd_is_writable(int fd) {
int r;
assert(fd >= 0);
r = fd_vet_accmode(fd, O_WRONLY);
if (r >= 0)
return true;
if (IN_SET(r, -EPROTOTYPE, -EBADFD, -EISDIR))
return false;
return r;
}
int fd_verify_safe_flags_full(int fd, int extra_flags) { int fd_verify_safe_flags_full(int fd, int extra_flags) {
int flags, unexpected_flags; int flags, unexpected_flags;

View File

@ -152,6 +152,7 @@ int fd_reopen_condition(int fd, int flags, int mask, int *ret_new_fd);
int fd_is_opath(int fd); int fd_is_opath(int fd);
int fd_vet_accmode(int fd, int mode); int fd_vet_accmode(int fd, int mode);
int fd_is_writable(int fd);
int fd_verify_safe_flags_full(int fd, int extra_flags); int fd_verify_safe_flags_full(int fd, int extra_flags);
static inline int fd_verify_safe_flags(int fd) { static inline int fd_verify_safe_flags(int fd) {

View File

@ -507,9 +507,6 @@ static int setup_output(
i = fixup_input(context, socket_fd, params->flags & EXEC_APPLY_TTY_STDIN); i = fixup_input(context, socket_fd, params->flags & EXEC_APPLY_TTY_STDIN);
o = fixup_output(context->std_output, socket_fd); o = fixup_output(context->std_output, socket_fd);
// FIXME: we probably should spend some time here to verify that if we inherit an fd from stdin
// (possibly indirect via inheritance from stdout) it is actually opened for write!
if (fileno == STDERR_FILENO) { if (fileno == STDERR_FILENO) {
ExecOutput e; ExecOutput e;
e = fixup_output(context->std_error, socket_fd); e = fixup_output(context->std_error, socket_fd);
@ -526,8 +523,17 @@ static int setup_output(
return fileno; return fileno;
/* Duplicate from stdout if possible */ /* Duplicate from stdout if possible */
if (can_inherit_stderr_from_stdout(context, o, e)) if (can_inherit_stderr_from_stdout(context, o, e)) {
r = fd_is_writable(STDOUT_FILENO);
if (r <= 0) {
if (r < 0)
log_warning_errno(r, "Failed to check if inherited stdout is writable for stderr, falling back to /dev/null.");
else
log_warning("Inherited stdout is not writable for stderr, falling back to /dev/null.");
return open_null_as(O_WRONLY, fileno);
}
return RET_NERRNO(dup2(STDOUT_FILENO, fileno)); return RET_NERRNO(dup2(STDOUT_FILENO, fileno));
}
o = e; o = e;
@ -537,8 +543,19 @@ static int setup_output(
return open_terminal_as(exec_context_tty_path(context), O_WRONLY, fileno); return open_terminal_as(exec_context_tty_path(context), O_WRONLY, fileno);
/* If the input is connected to anything that's not a /dev/null or a data fd, inherit that... */ /* If the input is connected to anything that's not a /dev/null or a data fd, inherit that... */
if (!IN_SET(i, EXEC_INPUT_NULL, EXEC_INPUT_DATA)) if (!IN_SET(i, EXEC_INPUT_NULL, EXEC_INPUT_DATA)) {
r = fd_is_writable(STDIN_FILENO);
if (r <= 0) {
if (r < 0)
log_warning_errno(r, "Failed to check if inherited stdin is writable for %s, falling back to /dev/null.",
fileno == STDOUT_FILENO ? "stdout" : "stderr");
else
log_warning("Inherited stdin is not writable for %s, falling back to /dev/null.",
fileno == STDOUT_FILENO ? "stdout" : "stderr");
return open_null_as(O_WRONLY, fileno);
}
return RET_NERRNO(dup2(STDIN_FILENO, fileno)); return RET_NERRNO(dup2(STDIN_FILENO, fileno));
}
/* If we are not started from PID 1 we just inherit STDOUT from our parent process. */ /* If we are not started from PID 1 we just inherit STDOUT from our parent process. */
if (getppid() != 1) if (getppid() != 1)
@ -554,8 +571,19 @@ static int setup_output(
return open_null_as(O_WRONLY, fileno); return open_null_as(O_WRONLY, fileno);
case EXEC_OUTPUT_TTY: case EXEC_OUTPUT_TTY:
if (exec_input_is_terminal(i)) if (exec_input_is_terminal(i)) {
r = fd_is_writable(STDIN_FILENO);
if (r <= 0) {
if (r < 0)
log_warning_errno(r, "Failed to check if inherited stdin is writable for TTY's %s, falling back to opening terminal.",
fileno == STDOUT_FILENO ? "stdout" : "stderr");
else
log_warning("Inherited stdin is not writable for TTY's %s, falling back to opening terminal.",
fileno == STDOUT_FILENO ? "stdout" : "stderr");
return open_terminal_as(exec_context_tty_path(context), O_WRONLY, fileno);
}
return RET_NERRNO(dup2(STDIN_FILENO, fileno)); return RET_NERRNO(dup2(STDIN_FILENO, fileno));
}
return open_terminal_as(exec_context_tty_path(context), O_WRONLY, fileno); return open_terminal_as(exec_context_tty_path(context), O_WRONLY, fileno);

View File

@ -1027,9 +1027,16 @@ static void socket_close_fds(Socket *s) {
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(Socket*, socket_close_fds, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(Socket*, socket_close_fds, NULL);
#define SOCKET_OPTION_WARNING_FORMAT_STR "Failed to set %s socket option, ignoring: %m" #define log_socket_option_errno(s, e, option) \
#define log_socket_option_warning_errno(s, error, option) \ ({ \
log_unit_warning_errno(UNIT(s), (error), SOCKET_OPTION_WARNING_FORMAT_STR, STRINGIFY(option)) int _e_ = (e); \
log_unit_full_errno( \
UNIT(s), \
ERRNO_IS_NOT_SUPPORTED(_e_) ? LOG_DEBUG : LOG_WARNING, \
_e_, \
"Failed to set %s socket option, ignoring: %m", \
option); \
})
static void socket_apply_socket_options(Socket *s, SocketPort *p, int fd) { static void socket_apply_socket_options(Socket *s, SocketPort *p, int fd) {
int r; int r;
@ -1041,82 +1048,79 @@ static void socket_apply_socket_options(Socket *s, SocketPort *p, int fd) {
if (s->keep_alive) { if (s->keep_alive) {
r = setsockopt_int(fd, SOL_SOCKET, SO_KEEPALIVE, true); r = setsockopt_int(fd, SOL_SOCKET, SO_KEEPALIVE, true);
if (r < 0) if (r < 0)
log_socket_option_warning_errno(s, r, SO_KEEPALIVE); log_socket_option_errno(s, r, "SO_KEEPALIVE");
} }
if (timestamp_is_set(s->keep_alive_time)) { if (timestamp_is_set(s->keep_alive_time)) {
r = setsockopt_int(fd, SOL_TCP, TCP_KEEPIDLE, s->keep_alive_time / USEC_PER_SEC); r = setsockopt_int(fd, SOL_TCP, TCP_KEEPIDLE, s->keep_alive_time / USEC_PER_SEC);
if (r < 0) if (r < 0)
log_socket_option_warning_errno(s, r, TCP_KEEPIDLE); log_socket_option_errno(s, r, "TCP_KEEPIDLE");
} }
if (s->keep_alive_interval > 0) { if (s->keep_alive_interval > 0) {
r = setsockopt_int(fd, SOL_TCP, TCP_KEEPINTVL, s->keep_alive_interval / USEC_PER_SEC); r = setsockopt_int(fd, SOL_TCP, TCP_KEEPINTVL, s->keep_alive_interval / USEC_PER_SEC);
if (r < 0) if (r < 0)
log_socket_option_warning_errno(s, r, TCP_KEEPINTVL); log_socket_option_errno(s, r, "TCP_KEEPINTVL");
} }
if (s->keep_alive_cnt > 0) { if (s->keep_alive_cnt > 0) {
r = setsockopt_int(fd, SOL_TCP, TCP_KEEPCNT, s->keep_alive_cnt); r = setsockopt_int(fd, SOL_TCP, TCP_KEEPCNT, s->keep_alive_cnt);
if (r < 0) if (r < 0)
log_socket_option_warning_errno(s, r, TCP_KEEPCNT); log_socket_option_errno(s, r, "TCP_KEEPCNT");
} }
if (s->defer_accept > 0) { if (s->defer_accept > 0) {
r = setsockopt_int(fd, SOL_TCP, TCP_DEFER_ACCEPT, s->defer_accept / USEC_PER_SEC); r = setsockopt_int(fd, SOL_TCP, TCP_DEFER_ACCEPT, s->defer_accept / USEC_PER_SEC);
if (r < 0) if (r < 0)
log_socket_option_warning_errno(s, r, TCP_DEFER_ACCEPT); log_socket_option_errno(s, r, "TCP_DEFER_ACCEPT");
} }
if (s->no_delay) { if (s->no_delay) {
if (s->socket_protocol == IPPROTO_SCTP) { if (s->socket_protocol == IPPROTO_SCTP) {
r = setsockopt_int(fd, SOL_SCTP, SCTP_NODELAY, true); r = setsockopt_int(fd, SOL_SCTP, SCTP_NODELAY, true);
if (r < 0) if (r < 0)
log_socket_option_warning_errno(s, r, SCTP_NODELAY); log_socket_option_errno(s, r, "SCTP_NODELAY");
} else { } else {
r = setsockopt_int(fd, SOL_TCP, TCP_NODELAY, true); r = setsockopt_int(fd, SOL_TCP, TCP_NODELAY, true);
if (r < 0) if (r < 0)
log_socket_option_warning_errno(s, r, TCP_NODELAY); log_socket_option_errno(s, r, "TCP_NODELAY");
} }
} }
if (s->broadcast) { if (s->broadcast) {
r = setsockopt_int(fd, SOL_SOCKET, SO_BROADCAST, true); r = setsockopt_int(fd, SOL_SOCKET, SO_BROADCAST, true);
if (r < 0) if (r < 0)
log_socket_option_warning_errno(s, r, SO_BROADCAST); log_socket_option_errno(s, r, "SO_BROADCAST");
} }
if (s->pass_cred) { if (s->pass_cred) {
r = setsockopt_int(fd, SOL_SOCKET, SO_PASSCRED, true); r = setsockopt_int(fd, SOL_SOCKET, SO_PASSCRED, true);
if (r < 0) if (r < 0)
log_socket_option_warning_errno(s, r, SO_PASSCRED); log_socket_option_errno(s, r, "SO_PASSCRED");
} }
if (s->pass_pidfd) { if (s->pass_pidfd) {
r = setsockopt_int(fd, SOL_SOCKET, SO_PASSPIDFD, true); r = setsockopt_int(fd, SOL_SOCKET, SO_PASSPIDFD, true);
if (r < 0) if (r < 0)
log_unit_full_errno(UNIT(s), ERRNO_IS_NEG_NOT_SUPPORTED(r) ? LOG_DEBUG : LOG_WARNING, r, log_socket_option_errno(s, r, "SO_PASSPIDFD");
SOCKET_OPTION_WARNING_FORMAT_STR, "SO_PASSPIDFD");
} }
if (s->pass_sec) { if (s->pass_sec) {
r = setsockopt_int(fd, SOL_SOCKET, SO_PASSSEC, true); r = setsockopt_int(fd, SOL_SOCKET, SO_PASSSEC, true);
if (r < 0) if (r < 0)
log_unit_full_errno(UNIT(s), ERRNO_IS_NEG_NOT_SUPPORTED(r) ? LOG_DEBUG : LOG_WARNING, r, log_socket_option_errno(s, r, "SO_PASSSEC");
SOCKET_OPTION_WARNING_FORMAT_STR, "SO_PASSSEC");
} }
if (s->pass_pktinfo) { if (s->pass_pktinfo) {
r = socket_set_recvpktinfo(fd, socket_address_family(&p->address), true); r = socket_set_recvpktinfo(fd, socket_address_family(&p->address), true);
if (r < 0) if (r < 0)
log_unit_warning_errno(UNIT(s), r, SOCKET_OPTION_WARNING_FORMAT_STR, "packet info"); log_socket_option_errno(s, r, "packet info");
} }
if (!s->pass_rights) { if (!s->pass_rights) {
r = setsockopt_int(fd, SOL_SOCKET, SO_PASSRIGHTS, false); r = setsockopt_int(fd, SOL_SOCKET, SO_PASSRIGHTS, false);
if (r < 0) if (r < 0)
log_unit_full_errno(UNIT(s), ERRNO_IS_NEG_NOT_SUPPORTED(r) ? LOG_DEBUG : LOG_WARNING, r, log_socket_option_errno(s, r, "SO_PASSRIGHTS");
SOCKET_OPTION_WARNING_FORMAT_STR, "SO_PASSRIGHTS");
} }
if (s->timestamping != SOCKET_TIMESTAMPING_OFF) { if (s->timestamping != SOCKET_TIMESTAMPING_OFF) {
@ -1124,61 +1128,59 @@ static void socket_apply_socket_options(Socket *s, SocketPort *p, int fd) {
s->timestamping == SOCKET_TIMESTAMPING_NS ? SO_TIMESTAMPNS : SO_TIMESTAMP, s->timestamping == SOCKET_TIMESTAMPING_NS ? SO_TIMESTAMPNS : SO_TIMESTAMP,
true); true);
if (r < 0) if (r < 0)
log_unit_warning_errno(UNIT(s), r, SOCKET_OPTION_WARNING_FORMAT_STR, "timestamping"); log_socket_option_errno(s, r, "timestamping");
} }
if (s->priority >= 0) { if (s->priority >= 0) {
r = setsockopt_int(fd, SOL_SOCKET, SO_PRIORITY, s->priority); r = setsockopt_int(fd, SOL_SOCKET, SO_PRIORITY, s->priority);
if (r < 0) if (r < 0)
log_socket_option_warning_errno(s, r, SO_PRIORITY); log_socket_option_errno(s, r, "SO_PRIORITY");
} }
if (s->receive_buffer > 0) { if (s->receive_buffer > 0) {
r = fd_set_rcvbuf(fd, s->receive_buffer, false); r = fd_set_rcvbuf(fd, s->receive_buffer, false);
if (r < 0) if (r < 0)
log_unit_full_errno(UNIT(s), ERRNO_IS_NEG_PRIVILEGE(r) ? LOG_DEBUG : LOG_WARNING, r, log_socket_option_errno(s, r, "SO_RCVBUF/SO_RCVBUFFORCE");
SOCKET_OPTION_WARNING_FORMAT_STR, "SO_RCVBUF/SO_RCVBUFFORCE");
} }
if (s->send_buffer > 0) { if (s->send_buffer > 0) {
r = fd_set_sndbuf(fd, s->send_buffer, false); r = fd_set_sndbuf(fd, s->send_buffer, false);
if (r < 0) if (r < 0)
log_unit_full_errno(UNIT(s), ERRNO_IS_NEG_PRIVILEGE(r) ? LOG_DEBUG : LOG_WARNING, r, log_socket_option_errno(s, r, "SO_SNDBUF/SO_SNDBUFFORCE");
SOCKET_OPTION_WARNING_FORMAT_STR, "SO_SNDBUF/SO_SNDBUFFORCE");
} }
if (s->mark >= 0) { if (s->mark >= 0) {
r = setsockopt_int(fd, SOL_SOCKET, SO_MARK, s->mark); r = setsockopt_int(fd, SOL_SOCKET, SO_MARK, s->mark);
if (r < 0) if (r < 0)
log_socket_option_warning_errno(s, r, SO_MARK); log_socket_option_errno(s, r, "SO_MARK");
} }
if (s->ip_tos >= 0) { if (s->ip_tos >= 0) {
r = setsockopt_int(fd, IPPROTO_IP, IP_TOS, s->ip_tos); r = setsockopt_int(fd, IPPROTO_IP, IP_TOS, s->ip_tos);
if (r < 0) if (r < 0)
log_socket_option_warning_errno(s, r, IP_TOS); log_socket_option_errno(s, r, "IP_TOS");
} }
if (s->ip_ttl >= 0) { if (s->ip_ttl >= 0) {
r = socket_set_ttl(fd, socket_address_family(&p->address), s->ip_ttl); r = socket_set_ttl(fd, socket_address_family(&p->address), s->ip_ttl);
if (r < 0) if (r < 0)
log_unit_warning_errno(UNIT(s), r, SOCKET_OPTION_WARNING_FORMAT_STR, "IP_TTL/IPV6_UNICAST_HOPS"); log_socket_option_errno(s, r, "IP_TTL/IPV6_UNICAST_HOPS");
} }
if (s->tcp_congestion) if (s->tcp_congestion)
if (setsockopt(fd, SOL_TCP, TCP_CONGESTION, s->tcp_congestion, strlen(s->tcp_congestion)+1) < 0) if (setsockopt(fd, SOL_TCP, TCP_CONGESTION, s->tcp_congestion, strlen(s->tcp_congestion)+1) < 0)
log_socket_option_warning_errno(s, errno, TCP_CONGESTION); log_socket_option_errno(s, errno, "TCP_CONGESTION");
if (s->smack_ip_in) { if (s->smack_ip_in) {
r = mac_smack_apply_fd(fd, SMACK_ATTR_IPIN, s->smack_ip_in); r = mac_smack_apply_fd(fd, SMACK_ATTR_IPIN, s->smack_ip_in);
if (r < 0) if (r < 0)
log_unit_error_errno(UNIT(s), r, "Failed to apply SMACK label for IP input, ignoring: %m"); log_unit_warning_errno(UNIT(s), r, "Failed to apply SMACK label for IP input, ignoring: %m");
} }
if (s->smack_ip_out) { if (s->smack_ip_out) {
r = mac_smack_apply_fd(fd, SMACK_ATTR_IPOUT, s->smack_ip_out); r = mac_smack_apply_fd(fd, SMACK_ATTR_IPOUT, s->smack_ip_out);
if (r < 0) if (r < 0)
log_unit_error_errno(UNIT(s), r, "Failed to apply SMACK label for IP output, ignoring: %m"); log_unit_warning_errno(UNIT(s), r, "Failed to apply SMACK label for IP output, ignoring: %m");
} }
} }

View File

@ -2008,6 +2008,12 @@ int config_parse_route_section(
if (streq(section, "Network")) { if (streq(section, "Network")) {
assert(streq_ptr(lvalue, "Gateway")); assert(streq_ptr(lvalue, "Gateway"));
/* Clear all previously defined routes when Gateway= (empty) is set in [Network] section */
if (isempty(rvalue)) {
network->routes_by_section = hashmap_free(network->routes_by_section);
return 0;
}
/* we are not in an Route section, so use line number instead */ /* we are not in an Route section, so use line number instead */
r = route_new_static(network, filename, line, &route); r = route_new_static(network, filename, line, &route);
} else } else

View File

@ -425,10 +425,17 @@ void test_prepare(int argc, char *argv[], int log_level) {
test_setup_logging(log_level); test_setup_logging(log_level);
} }
int assert_signal_internal(void) { /* Returns:
* ASSERT_SIGNAL_FORK_CHILD = We are in the child process
* ASSERT_SIGNAL_FORK_PARENT = We are in the parent process (signal/status stored in *ret_signal)
* <0 = Error (negative errno)
*/
int assert_signal_internal(int *ret_signal) {
siginfo_t siginfo = {}; siginfo_t siginfo = {};
int r; int r;
assert(ret_signal);
r = fork(); r = fork();
if (r < 0) if (r < 0)
return -errno; return -errno;
@ -439,14 +446,23 @@ int assert_signal_internal(void) {
/* But still set an rlimit just in case */ /* But still set an rlimit just in case */
(void) setrlimit(RLIMIT_CORE, &RLIMIT_MAKE_CONST(0)); (void) setrlimit(RLIMIT_CORE, &RLIMIT_MAKE_CONST(0));
return 0; return ASSERT_SIGNAL_FORK_CHILD;
} }
r = wait_for_terminate(r, &siginfo); r = wait_for_terminate(r, &siginfo);
if (r < 0) if (r < 0)
return r; return r;
return siginfo.si_status; /* si_status means different things depending on si_code:
* - CLD_EXITED: si_status is the exit code
* - CLD_KILLED/CLD_DUMPED: si_status is the signal number that killed the process
* We need to return the signal number only if the child was killed by a signal. */
if (IN_SET(siginfo.si_code, CLD_KILLED, CLD_DUMPED))
*ret_signal = siginfo.si_status;
else
*ret_signal = 0;
return ASSERT_SIGNAL_FORK_PARENT;
} }

View File

@ -592,23 +592,33 @@ _noreturn_ void log_test_failed_internal(const char *file, int line, const char
}) })
#endif #endif
int assert_signal_internal(void); enum {
ASSERT_SIGNAL_FORK_CHILD = 0, /* We are in the child process */
ASSERT_SIGNAL_FORK_PARENT = 1, /* We are in the parent process */
};
int assert_signal_internal(int *ret_status);
#ifdef __COVERITY__ #ifdef __COVERITY__
# define ASSERT_SIGNAL(expr, signal) __coverity_check__(((expr), false)) # define ASSERT_SIGNAL(expr, signal) __coverity_check__(((expr), false))
#else #else
# define ASSERT_SIGNAL(expr, signal) \ # define ASSERT_SIGNAL(expr, signal) __ASSERT_SIGNAL(UNIQ, expr, signal)
# define __ASSERT_SIGNAL(uniq, expr, sgnl) \
({ \ ({ \
ASSERT_TRUE(SIGNAL_VALID(signal)); \ ASSERT_TRUE(SIGNAL_VALID(sgnl)); \
int _r = assert_signal_internal(); \ int UNIQ_T(_status, uniq); \
ASSERT_OK_ERRNO(_r); \ int UNIQ_T(_path, uniq) = assert_signal_internal(&UNIQ_T(_status, uniq)); \
if (_r == 0) { \ ASSERT_OK_ERRNO(UNIQ_T(_path, uniq)); \
if (UNIQ_T(_path, uniq) == ASSERT_SIGNAL_FORK_CHILD) { \
(void) signal(sgnl, SIG_DFL); \
expr; \ expr; \
_exit(EXIT_SUCCESS); \ _exit(EXIT_SUCCESS); \
} \ } \
if (_r != signal) \ ASSERT_EQ(UNIQ_T(_path, uniq), ASSERT_SIGNAL_FORK_PARENT); \
if (UNIQ_T(_status, uniq) != sgnl) \
log_test_failed("\"%s\" died with signal %s, but %s was expected", \ log_test_failed("\"%s\" died with signal %s, but %s was expected", \
#expr, signal_to_string(_r), signal_to_string(signal)); \ #expr, signal_to_string(UNIQ_T(_status, uniq)), \
signal_to_string(sgnl)); \
}) })
#endif #endif

View File

@ -903,4 +903,27 @@ TEST(fd_vet_accmode) {
ASSERT_ERROR(fd_vet_accmode(fd_opath, O_RDWR), EBADFD); ASSERT_ERROR(fd_vet_accmode(fd_opath, O_RDWR), EBADFD);
} }
TEST(fd_is_writable) {
_cleanup_(unlink_tempfilep) char name[] = "/tmp/test-fd-writable.XXXXXX";
_cleanup_close_ int fd_ro = -EBADF, fd_wo = -EBADF, fd_rw = -EBADF, fd_path = -EBADF;
ASSERT_OK(fd_rw = mkostemp_safe(name));
ASSERT_OK_POSITIVE(fd_is_writable(fd_rw));
ASSERT_OK(fd_ro = open(name, O_RDONLY | O_CLOEXEC));
ASSERT_OK_ZERO(fd_is_writable(fd_ro));
ASSERT_OK(fd_wo = open(name, O_WRONLY | O_CLOEXEC));
ASSERT_OK_POSITIVE(fd_is_writable(fd_wo));
ASSERT_OK(fd_path = open(name, O_PATH | O_CLOEXEC));
ASSERT_OK_ZERO(fd_is_writable(fd_path));
ASSERT_SIGNAL(fd_is_writable(-1), SIGABRT);
safe_close(fd_ro);
ASSERT_ERROR(fd_is_writable(fd_ro), EBADF);
TAKE_FD(fd_ro);
}
DEFINE_TEST_MAIN(LOG_DEBUG); DEFINE_TEST_MAIN(LOG_DEBUG);

View File

@ -137,4 +137,79 @@ TEST(ASSERT_OK_OR) {
ASSERT_SIGNAL(ASSERT_OK_OR(-1, -2), SIGABRT); ASSERT_SIGNAL(ASSERT_OK_OR(-1, -2), SIGABRT);
} }
/* Regression test for issue where assert_signal_internal() wasn't checking si_code before returning
* si_status.
*
* In the bug case, siginfo.si_status has different meanings depending on siginfo.si_code:
*
* - If si_code == CLD_EXITED: si_status is the exit code (0-255)
* - If si_code == CLD_KILLED/CLD_DUMPED: si_status is the signal number
*
* In the bug case where st_code is not checked, exit codes would be confused with signal numbers. For
* example, if a child exits with code 6, it would incorrectly look like SIGABRT.
*
* This test verifies that exit codes are NOT confused with signal numbers, even when the exit code
* numerically matches a signal number.
*/
TEST(ASSERT_SIGNAL_exit_code_vs_signal) {
/* These exit codes numerically match common signal numbers, but ASSERT_SIGNAL should correctly
* identify them as exit codes (si_code==CLD_EXITED), not signals. The inner ASSERT_SIGNAL expects a
* signal but gets an exit code, so it should fail (aborting with SIGABRT), which the outer
* ASSERT_SIGNAL then catches. */
ASSERT_SIGNAL(ASSERT_SIGNAL(_exit(6), SIGABRT), SIGABRT); /* 6 = SIGABRT */
ASSERT_SIGNAL(ASSERT_SIGNAL(_exit(9), SIGKILL), SIGABRT); /* 9 = SIGKILL */
ASSERT_SIGNAL(ASSERT_SIGNAL(_exit(11), SIGSEGV), SIGABRT); /* 11 = SIGSEGV */
ASSERT_SIGNAL(ASSERT_SIGNAL(_exit(15), SIGTERM), SIGABRT); /* 15 = SIGTERM */
/* _exit(0) should not be confused with any signal */
ASSERT_SIGNAL(ASSERT_SIGNAL(_exit(0), SIGABRT), SIGABRT);
}
/* Regression test for issue where returning 0 from assert_signal_internal() was ambiguous.
*
* In the bug case, when assert_signal_internal() returned 0, it could mean two different things:
*
* 1. We're in the child process (fork() just returned 0)
* 2. We're in the parent and the child exited normally (no signal)
*
* The ASSERT_SIGNAL macro couldn't distinguish between these cases. When case #2 occurred, the macro would
* re-enter the "if (_r == 0)" block, re-run the expression in the parent, and call _exit(EXIT_SUCCESS),
* causing tests to incorrectly pass even when no signal occurred.
*
* The fix separates the question of which process we are in from which signal occurred:
*
* - assert_signal_internal() now returns ASSERT_SIGNAL_FORK_CHILD (0) or ASSERT_SIGNAL_FORK_PARENT (1) to
* indicate execution path
* - The actual signal/status is passed via an output parameter (*ret_status)
*
* This allows the macro to unambiguously distinguish between being the child (path ==
* ASSERT_SIGNAL_FORK_CHILD) and being the parent when the child has exited normally (path ==
* ASSERT_SIGNAL_FORK_PARENT && status == 0).
*
* This test verifies that when a child exits normally (with exit code 0), ASSERT_SIGNAL correctly detects
* that NO signal was raised, rather than being confused and thinking it's still in the child process.
*/
TEST(ASSERT_SIGNAL_exit_vs_child_process) {
/* When a child calls _exit(0), it exits normally with code 0 (no signal). The parent's
* assert_signal_internal() returns ASSERT_SIGNAL_FORK_PARENT, and sets ret_status to 0, meaning
* there was no signal. This should NOT be confused with being the child process. The inner
* ASSERT_SIGNAL expects SIGABRT but sees no signal, so it should fail, which the outerj
* ASSERT_SIGNAL catches. */
ASSERT_SIGNAL(ASSERT_SIGNAL(_exit(EXIT_SUCCESS), SIGABRT), SIGABRT);
}
TEST(ASSERT_SIGNAL_basic) {
/* Correct behavior: expression raises expected signal */
ASSERT_SIGNAL(abort(), SIGABRT);
ASSERT_SIGNAL(raise(SIGTERM), SIGTERM);
ASSERT_SIGNAL(raise(SIGSEGV), SIGSEGV);
ASSERT_SIGNAL(raise(SIGILL), SIGILL);
/* Wrong signal: inner ASSERT_SIGNAL expects SIGABRT but gets SIGTERM, so it fails (aborts), which
* outer ASSERT_SIGNAL catches. */
ASSERT_SIGNAL(ASSERT_SIGNAL(raise(SIGTERM), SIGABRT), SIGABRT);
ASSERT_SIGNAL(ASSERT_SIGNAL(raise(SIGKILL), SIGTERM), SIGABRT);
}
DEFINE_TEST_MAIN(LOG_INFO); DEFINE_TEST_MAIN(LOG_INFO);

View File

@ -0,0 +1,18 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Match]
Name=dummy98
[Network]
Address=10.0.0.1/24
Gateway=10.0.0.2
[Route]
Destination=192.168.1.0/24
Gateway=10.0.0.254
[Route]
Destination=192.168.2.0/24
Gateway=10.0.0.253
[Network]
Gateway=

View File

@ -4633,6 +4633,21 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
self.assertNotIn('149.10.124.59', output) self.assertNotIn('149.10.124.59', output)
self.assertIn('default via 149.10.124.60 proto static', output) self.assertIn('default via 149.10.124.60 proto static', output)
def test_gateway_clear_routes(self):
copy_network_unit('25-gateway-clear-routes.network', '12-dummy.netdev')
start_networkd()
self.wait_online('dummy98:routable')
print('### ip -4 route show dev dummy98')
output = check_output('ip -4 route show dev dummy98')
print(output)
# All routes should be cleared - no default gateway, no [Route] section routes
self.assertNotIn('default via 10.0.0.2', output)
self.assertNotIn('192.168.1.0/24', output)
self.assertNotIn('192.168.2.0/24', output)
# Only the directly connected network should remain
self.assertIn('10.0.0.0/24 proto kernel scope link src 10.0.0.1', output)
def test_ip_route_ipv6_src_route(self): def test_ip_route_ipv6_src_route(self):
# a dummy device does not make the addresses go through tentative state, so we # a dummy device does not make the addresses go through tentative state, so we
# reuse a bond from an earlier test, which does make the addresses go through # reuse a bond from an earlier test, which does make the addresses go through

View File

@ -207,7 +207,7 @@ varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "
(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "multi-user.target", "pid": {"pid": 1}}') (! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "multi-user.target", "pid": {"pid": 1}}')
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"cgroup": "/init.scope"}' varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"cgroup": "/init.scope"}'
invocation_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | .runtime.InvocationID' | grep -v null | tail -n 1) invocation_id="$(systemctl show -P InvocationID systemd-journald.service)"
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "{\"invocationID\": \"$invocation_id\"}" varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "{\"invocationID\": \"$invocation_id\"}"
# test io.systemd.Manager in user manager # test io.systemd.Manager in user manager