mirror of
https://github.com/systemd/systemd
synced 2026-03-30 11:44:49 +02:00
Compare commits
13 Commits
c6c43d677a
...
3f91ffe0fe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f91ffe0fe | ||
|
|
5b0e262f45 | ||
|
|
9d7a70003d | ||
|
|
0b7dfc036d | ||
|
|
6d36d07599 | ||
|
|
e21a431ec4 | ||
|
|
d759ed527c | ||
|
|
39adecfcd8 | ||
|
|
171ceb4a00 | ||
|
|
592c57e586 | ||
|
|
f7df0eab8d | ||
|
|
8d50438ba5 | ||
|
|
408e8d361f |
@ -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)
|
|
||||||
```
|
|
||||||
@ -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.)
|
|
||||||
@ -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>
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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;
|
||||||
|
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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);
|
||||||
|
|
||||||
|
|||||||
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
18
test/test-network/conf/25-gateway-clear-routes.network
Normal file
18
test/test-network/conf/25-gateway-clear-routes.network
Normal 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=
|
||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user