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

Compare commits

..

No commits in common. "3f91ffe0fe900660a8c073ec54b3951e33b5c74c" and "c6c43d677abe66c404a2fb4aa66707bcabff4dff" have entirely different histories.

16 changed files with 461 additions and 264 deletions

View File

@ -1 +1,176 @@
[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 +1,236 @@
[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,7 +120,6 @@
<literal>development</literal>,
<literal>integration</literal>,
<literal>staging</literal>,
<literal>testing</literal>,
<literal>production</literal>.
</para>

View File

@ -747,9 +747,6 @@ DuplicateAddressDetection=none</programlisting></para>
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>
<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"/>
</listitem>
</varlistentry>

View File

@ -998,21 +998,6 @@ int fd_vet_accmode(int fd, int mode) {
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 flags, unexpected_flags;

View File

@ -152,7 +152,6 @@ int fd_reopen_condition(int fd, int flags, int mask, int *ret_new_fd);
int fd_is_opath(int fd);
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);
static inline int fd_verify_safe_flags(int fd) {

View File

@ -507,6 +507,9 @@ static int setup_output(
i = fixup_input(context, socket_fd, params->flags & EXEC_APPLY_TTY_STDIN);
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) {
ExecOutput e;
e = fixup_output(context->std_error, socket_fd);
@ -523,17 +526,8 @@ static int setup_output(
return fileno;
/* Duplicate from stdout if possible */
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);
}
if (can_inherit_stderr_from_stdout(context, o, e))
return RET_NERRNO(dup2(STDOUT_FILENO, fileno));
}
o = e;
@ -543,19 +537,8 @@ static int setup_output(
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 (!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);
}
if (!IN_SET(i, EXEC_INPUT_NULL, EXEC_INPUT_DATA))
return RET_NERRNO(dup2(STDIN_FILENO, fileno));
}
/* If we are not started from PID 1 we just inherit STDOUT from our parent process. */
if (getppid() != 1)
@ -571,19 +554,8 @@ static int setup_output(
return open_null_as(O_WRONLY, fileno);
case EXEC_OUTPUT_TTY:
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);
}
if (exec_input_is_terminal(i))
return RET_NERRNO(dup2(STDIN_FILENO, fileno));
}
return open_terminal_as(exec_context_tty_path(context), O_WRONLY, fileno);

View File

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

View File

@ -2008,12 +2008,6 @@ int config_parse_route_section(
if (streq(section, "Network")) {
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 */
r = route_new_static(network, filename, line, &route);
} else

View File

@ -425,17 +425,10 @@ void test_prepare(int argc, char *argv[], int log_level) {
test_setup_logging(log_level);
}
/* 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) {
int assert_signal_internal(void) {
siginfo_t siginfo = {};
int r;
assert(ret_signal);
r = fork();
if (r < 0)
return -errno;
@ -446,23 +439,14 @@ int assert_signal_internal(int *ret_signal) {
/* But still set an rlimit just in case */
(void) setrlimit(RLIMIT_CORE, &RLIMIT_MAKE_CONST(0));
return ASSERT_SIGNAL_FORK_CHILD;
return 0;
}
r = wait_for_terminate(r, &siginfo);
if (r < 0)
return r;
/* 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;
return siginfo.si_status;
}

View File

@ -592,33 +592,23 @@ _noreturn_ void log_test_failed_internal(const char *file, int line, const char
})
#endif
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);
int assert_signal_internal(void);
#ifdef __COVERITY__
# define ASSERT_SIGNAL(expr, signal) __coverity_check__(((expr), false))
#else
# define ASSERT_SIGNAL(expr, signal) __ASSERT_SIGNAL(UNIQ, expr, signal)
# define __ASSERT_SIGNAL(uniq, expr, sgnl) \
# define ASSERT_SIGNAL(expr, signal) \
({ \
ASSERT_TRUE(SIGNAL_VALID(sgnl)); \
int UNIQ_T(_status, uniq); \
int UNIQ_T(_path, uniq) = assert_signal_internal(&UNIQ_T(_status, uniq)); \
ASSERT_OK_ERRNO(UNIQ_T(_path, uniq)); \
if (UNIQ_T(_path, uniq) == ASSERT_SIGNAL_FORK_CHILD) { \
(void) signal(sgnl, SIG_DFL); \
ASSERT_TRUE(SIGNAL_VALID(signal)); \
int _r = assert_signal_internal(); \
ASSERT_OK_ERRNO(_r); \
if (_r == 0) { \
expr; \
_exit(EXIT_SUCCESS); \
} \
ASSERT_EQ(UNIQ_T(_path, uniq), ASSERT_SIGNAL_FORK_PARENT); \
if (UNIQ_T(_status, uniq) != sgnl) \
if (_r != signal) \
log_test_failed("\"%s\" died with signal %s, but %s was expected", \
#expr, signal_to_string(UNIQ_T(_status, uniq)), \
signal_to_string(sgnl)); \
#expr, signal_to_string(_r), signal_to_string(signal)); \
})
#endif

View File

@ -903,27 +903,4 @@ TEST(fd_vet_accmode) {
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);

View File

@ -137,79 +137,4 @@ TEST(ASSERT_OK_OR) {
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);

View File

@ -1,18 +0,0 @@
# 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,21 +4633,6 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
self.assertNotIn('149.10.124.59', 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):
# 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

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 '{"cgroup": "/init.scope"}'
invocation_id="$(systemctl show -P InvocationID systemd-journald.service)"
invocation_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | .runtime.InvocationID' | grep -v null | tail -n 1)
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "{\"invocationID\": \"$invocation_id\"}"
# test io.systemd.Manager in user manager