1
0
mirror of https://github.com/systemd/systemd synced 2026-03-27 09:14:51 +01:00

Compare commits

..

11 Commits

Author SHA1 Message Date
Lennart Poettering
7d50cd65bb
Merge pull request #20465 from bluca/portable_validate_sysext
portabled: validate SYSEXT_LEVEL when attaching
2021-09-06 21:10:15 +02:00
Lennart Poettering
f0a8ec4372
Merge pull request #20527 from systemd/wip/hadess/usb-analysers-uaccess
hwdb: Allow end-users root-less access to USB analysers
2021-09-06 21:06:40 +02:00
Maanya Goenka
4b4a8ef741 systemd-analyze: add new option to generate JSON output of security analysis table
The new option --json= works with the 'security' verb and takes in one of three format flags.
These are off which is the default, pretty and short which use JSON format flags for output.
When set to true, it generates a JSON formatted output of the security analysis table. The
format is a JSON array with objects containing the following fields: set which indicates if
the id has been set or not, name which is what is used to refer to the id, json_field
which is the equivalent JSON formatted id name only used for JSON outputs, description which
is an outline of the id state, and exposure which is an unsigned integer in the range 0.0..10.0,
where a higher value corresponds to a higher security threat. The JSON version of the table is
printed on the standard output file.

Example Run:

The unit file testfile.service was created to test the --json= option

maanya-goenka@debian:~/systemd (json-security)$ cat <<EOF >testfile.service

> [Service]
> ExecStart = echo hello
> PrivateNetwork = yes
> PrivateMounts = yes
> PrivateDevices = yes
> EOF

Both the JSON output and the security analysis table below have been truncated to increase readability.
1. Testing for when --json=off

maanya-goenka@debian:~/systemd (json-security)$ sudo build/systemd-analyze security --json=off --root= --offline=true
testfile.service --no-pager

/usr/lib/systemd/system/plymouth-start.service:15: Unit configured to use KillMode=none. This is unsafe, as it disables systemd's
process lifecycle management for the service. Please update your service to use a safer KillMode=, such as 'mixed' or 'control-group'.
Support for KillMode=none is deprecated and will eventually be removed.
/usr/lib/systemd/system/dbus.socket:5: ListenStream= references a path below legacy directory /var/run/, updating
/var/run/dbus/system_bus_socket → /run/dbus/system_bus_socket; please update the unit file accordingly.
/usr/lib/systemd/system/gdm.service:30: Standard output type syslog is obsolete, automatically updating to journal. Please update your
unit file, and consider removing the setting altogether.
/home/maanya-goenka/systemd/foo.service:2: Unknown key name 'foo' in section 'Unit', ignoring.

    NAME                                                      DESCRIPTION                                                       EXPOSURE
✓   PrivateNetwork=                                           Service has no access to the host's network
✗   User=/DynamicUser=                                        Service runs as root user                                              0.4
✗   CapabilityBoundingSet=~CAP_SET(UID|GID|PCAP)              Service may change UID/GID identities/capabilities                     0.3
✗   CapabilityBoundingSet=~CAP_NET_ADMIN                      Service has administrator privileges                                   0.3

→ Overall exposure level for testfile.service: 8.3 EXPOSED 🙁

2. Testing for when --json=pretty

maanya-goenka@debian:~/systemd (json-security)$ sudo build/systemd-analyze security --json=pretty --root= --offline=true
testfile.service

/usr/lib/systemd/system/plymouth-start.service:15: Unit configured to use KillMode=none. This is unsafe, as it disables systemd's
process lifecycle management for the service. Please update your service to use a safer KillMode=, such as 'mixed' or 'control-group'.
Support for KillMode=none is deprecated and will eventually be removed.
/usr/lib/systemd/system/dbus.socket:5: ListenStream= references a path below legacy directory /var/run/, updating
/var/run/dbus/system_bus_socket → /run/dbus/system_bus_socket; please update the unit file accordingly.
/usr/lib/systemd/system/gdm.service:30: Standard output type syslog is obsolete, automatically updating to journal. Please update your
unit file, and consider removing the setting altogether.
/home/maanya-goenka/systemd/foo.service:2: Unknown key name 'foo' in section 'Unit', ignoring.

[
        {
                "set" : true,
                "name" : "PrivateNetwork=",
		"json-field" : "PrivateNetwork",
                "description" : "Service has no access to the host's network",
                "exposure" : null
        },
        {
                "set" : false,
                "name" : "User=/DynamicUser=",
		"json-field" : "UserOrDynamicUser",
                "decsription" : "Service runs as root user",
                "exposure" : "0.4"
        },
        {
                "set" : false,
                "name" : "CapabilityBoundingSet=~CAP_SET(UID|GID|PCAP)",
		"json_field" : "CapabilityBoundingSet_CAP_SET_UID_GID_PCAP",
                "description" : "Service may change UID/GID identities/capabilities",
                "exposure" : "0.3"
        },
        {
                "set" : false,
                "name" : "CapabilityBoundingSet=~CAP_NET_ADMIN",
		"json_field" : "CapabilityBoundingSet_CAP_NET_ADMIN",
                "description" : "Service has administrator privileges",
                "exposure" : "0.3"
        },
        ...
]

3. Testing for when --json=short

maanya-goenka@debian:~/systemd (json-security)$ sudo build/systemd-analyze security --json=short --root= --offline=true
testfile.service

/usr/lib/systemd/system/plymouth-start.service:15: Unit configured to use KillMode=none. This is unsafe, as it disables systemd's
process lifecycle management for the service. Please update your service to use a safer KillMode=, such as 'mixed' or 'control-group'.
Support for KillMode=none is deprecated and will eventually be removed.
/usr/lib/systemd/system/dbus.socket:5: ListenStream= references a path below legacy directory /var/run/, updating
/var/run/dbus/system_bus_socket → /run/dbus/system_bus_socket; please update the unit file accordingly.
/usr/lib/systemd/system/gdm.service:30: Standard output type syslog is obsolete, automatically updating to journal. Please update your
unit file, and consider removing the setting altogether.
/home/maanya-goenka/systemd/foo.service:2: Unknown key name 'foo' in section 'Unit', ignoring.

[{"set":true,"name":"PrivateNetwork=", "json_field":"PrivateNetwork", "description":"Service has no access to the host's network","exposure":null}, ...]
2021-09-06 19:55:27 +01:00
Kyle Laker
c1e6f21556
systemd-analyze: use config value in RestrictNamespaces id (#20645)
For most fields, the text shown by `.id` is the value that should be set
in the unit file; however, for RestrictNamespaces, it is not. Changing
this to show the actual text makes it more clear to a user what the
actual change that needs to be made to the unit file is.
2021-09-06 17:33:16 +02:00
Ross Jennings
f77e015a88
Fix volume control keys for LG Gram (#20644)
Fix volume control keys for LG Gram
2021-09-06 16:55:11 +02:00
Luca Boccassi
9ff61565be portabled: refactor extraction/validation into a common helper 2021-09-06 13:20:01 +01:00
Luca Boccassi
239ac0c7f7 portabled: validate SYSEXT_LEVEL when attaching
When attaching a portable service with extensions, immediately validate
that the os-release and extension-release metadata values match, rather
than letting it fail when the units are started
2021-09-06 12:14:50 +01:00
Luca Boccassi
7bf5ec4538 portabled: error out if there are no units only after parsing all images
It's ok if the OS image doesn't have matching units, if we find them
in the extensions. Tidies up the parsing logic a bit.
2021-09-06 12:13:53 +01:00
Luca Boccassi
9ccb531a5f dissect-image: add extension-specific validation flag
Allows callers to specify which image type they are looking for
2021-09-06 12:13:53 +01:00
Bastien Nocera
9e2dbfef47 hwdb: Allow end-users root-less access to USB analyzers
Procotol analyzers are external devices used to capture traffic over a
wire so that it could be analysed. End-users at the console should be
able to access those devices without requiring root access.

This change obsoletes the need to install Total Phase's "Linux drivers",
which are really just udev rules and hotplug usermap files to do that:
https://www.totalphase.com/products/usb-drivers-linux/
2021-09-02 16:01:28 +02:00
Bastien Nocera
94cb45d57f udev: Import hwdb matches for USB devices
Import hwdb matches for USB devices (not interfaces) which don't usually
have a modalias so that it's possible to, for example, make them
available for unprivileged users.
2021-09-01 15:49:34 +02:00
19 changed files with 371 additions and 138 deletions

View File

@ -930,6 +930,15 @@ evdev:input:b0003v04B3p301B*
evdev:name:SIPODEV Lenovo HID Device:dmi:*:svnLENOVO:*:pvrLenovoideapadD330-10IGM:*
KEYBOARD_KEY_70073=f21 # Fn+Supr (Touchpad toggle)
###########################################################
# LG
###########################################################
# LG Gram
evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLGElectronics:pn1*.AAS*:*
KEYBOARD_KEY_a0=!mute
KEYBOARD_KEY_ae=!volumedown
KEYBOARD_KEY_b0=!volumeup
###########################################################
# Logitech

33
hwdb.d/70-analyzers.hwdb Normal file
View File

@ -0,0 +1,33 @@
# This file is part of systemd.
#
# Database for signal analyzers (protocol analyzers, logic analyzers,
# oscilloscopes, multimeters, bench power supplies, etc.) that should
# be accessible to the seat owner.
#
# Permitted keys:
# Specify if a device is a signal analyzer
# ID_SIGNAL_ANALYZER=1|0
###########################################################
# Total Phase
###########################################################
# Aarvark I2C/SPI Host Adapter
usb:v0403pe0d0*
ID_SIGNAL_ANALYZER=1
# Beagle Protocol Analyzers
usb:v1679p2001*
ID_SIGNAL_ANALYZER=1
# Cheetah SPI Host Adapter
usb:v1679p2002*
ID_SIGNAL_ANALYZER=1
# Komodo CAN Duo Interface
usb:v1679p3001*
ID_SIGNAL_ANALYZER=1
# Power Delivery Analyzers
usb:v1679p6003*
usb:v0483pdf11*
ID_SIGNAL_ANALYZER=1

View File

@ -27,6 +27,7 @@ hwdb_files_test = files('''
60-keyboard.hwdb
60-seat.hwdb
60-sensor.hwdb
70-analyzers.hwdb
70-joystick.hwdb
70-mouse.hwdb
70-pointingstick.hwdb

View File

@ -147,6 +147,7 @@ def property_grammar():
('ID_INPUT_TOUCHPAD', Or((Literal('0'), Literal('1')))),
('ID_INPUT_TOUCHSCREEN', Or((Literal('0'), Literal('1')))),
('ID_INPUT_TRACKBALL', Or((Literal('0'), Literal('1')))),
('ID_SIGNAL_ANALYZER', Or((Literal('0'), Literal('1')))),
('POINTINGSTICK_SENSITIVITY', INTEGER),
('POINTINGSTICK_CONST_ACCEL', REAL),
('ID_INPUT_JOYSTICK_INTEGRATION', Or(('internal', 'external'))),

View File

@ -967,25 +967,25 @@ Service b@0.service not loaded, b.socket cannot be started.
<entry>RestrictSUIDSGID</entry>
</row>
<row>
<entry>RestrictNamespaces_CLONE_NEWUSER</entry>
<entry>RestrictNamespaces_user</entry>
</row>
<row>
<entry>RestrictNamespaces_CLONE_NEWNS</entry>
<entry>RestrictNamespaces_mnt</entry>
</row>
<row>
<entry>RestrictNamespaces_CLONE_NEWIPC</entry>
<entry>RestrictNamespaces_ipc</entry>
</row>
<row>
<entry>RestrictNamespaces_CLONE_NEWPID</entry>
<entry>RestrictNamespaces_pid</entry>
</row>
<row>
<entry>RestrictNamespaces_CLONE_NEWCGROUP</entry>
<entry>RestrictNamespaces_cgroup</entry>
</row>
<row>
<entry>RestrictNamespaces_CLONE_NEWUTS</entry>
<entry>RestrictNamespaces_uts</entry>
</row>
<row>
<entry>RestrictNamespaces_CLONE_NEWNET</entry>
<entry>RestrictNamespaces_net</entry>
</row>
<row>
<entry>RestrictAddressFamilies_AF_INET_INET6</entry>
@ -1109,6 +1109,22 @@ Service b@0.service not loaded, b.socket cannot be started.
</varlistentry>
<varlistentry>
<term><option>--json=<replaceable>MODE</replaceable></option></term>
<listitem><para>With the <command>security</command> command, generate a JSON formatted
output of the security analysis table. The format is a JSON array with objects
containing the following fields: <varname>set</varname> which indicates if the setting has
been enabled or not, <varname>name</varname> which is what is used to refer to the setting,
<varname>json_field</varname> which is the JSON compatible identifier of the setting,
<varname>description</varname> which is an outline of the setting state, and
<varname>exposure</varname> which is a number in the range 0.0…10.0, where a higher value
corresponds to a higher security threat. The JSON version of the table is printed to standard
output. The <replaceable>MODE</replaceable> passed to the option can be one of three:
<option>off</option> which is the default, <option>pretty</option> and <option>short</option>
which respectively output a prettified or shorted JSON version of the security table.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--iterations=<replaceable>NUMBER</replaceable></option></term>

View File

@ -12,6 +12,7 @@ SUBSYSTEM=="rtc", KERNEL=="rtc0", SYMLINK+="rtc", OPTIONS+="link_priority=-100"
SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", IMPORT{builtin}="usb_id", IMPORT{builtin}="hwdb --subsystem=usb"
ENV{MODALIAS}!="", IMPORT{builtin}="hwdb --subsystem=$env{SUBSYSTEM}"
SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", IMPORT{builtin}="hwdb 'usb:v$attr{idVendor}p$attr{idProduct}'"
ACTION!="add", GOTO="default_end"

View File

@ -145,7 +145,7 @@ _systemd_analyze() {
elif __contains_word "$verb" ${VERBS[SECURITY]}; then
if [[ $cur = -* ]]; then
comps='--help --version --no-pager --system --user -H --host -M --machine --offline --threshold --security-policy'
comps='--help --version --no-pager --system --user -H --host -M --machine --offline --threshold --security-policy --json=off --json=pretty --json=short'
else
if __contains_word "--user" ${COMP_WORDS[*]}; then
mode=--user

View File

@ -93,6 +93,7 @@ _arguments \
'--offline=[Perform a security review of the specified unit file(s)]:BOOL' \
'--threshold=[Set a value to compare the overall security exposure level with]: NUMBER' \
'--security-policy=[Allow user to use customized requirements to compare unit file(s) against]: PATH' \
'--json=[Generate a JSON output of the security analysis table]:MODE:(pretty short off)' \
'--no-pager[Do not pipe output into a pager]' \
'--man=[Do (not) check for existence of man pages]:boolean:(1 0)' \
'--order[When generating graph for dot, show only order]' \

View File

@ -1340,8 +1340,8 @@ static const struct security_assessor security_assessor_table[] = {
.offset = offsetof(SecurityInfo, restrict_suid_sgid),
},
{
.id = "RestrictNamespaces=~CLONE_NEWUSER",
.json_field = "RestrictNamespaces_CLONE_NEWUSER",
.id = "RestrictNamespaces=~user",
.json_field = "RestrictNamespaces_user",
.url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
.description_good = "Service cannot create user namespaces",
.description_bad = "Service may create user namespaces",
@ -1351,8 +1351,8 @@ static const struct security_assessor security_assessor_table[] = {
.parameter = CLONE_NEWUSER,
},
{
.id = "RestrictNamespaces=~CLONE_NEWNS",
.json_field = "RestrictNamespaces_CLONE_NEWNS",
.id = "RestrictNamespaces=~mnt",
.json_field = "RestrictNamespaces_mnt",
.url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
.description_good = "Service cannot create file system namespaces",
.description_bad = "Service may create file system namespaces",
@ -1362,8 +1362,8 @@ static const struct security_assessor security_assessor_table[] = {
.parameter = CLONE_NEWNS,
},
{
.id = "RestrictNamespaces=~CLONE_NEWIPC",
.json_field = "RestrictNamespaces_CLONE_NEWIPC",
.id = "RestrictNamespaces=~ipc",
.json_field = "RestrictNamespaces_ipc",
.url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
.description_good = "Service cannot create IPC namespaces",
.description_bad = "Service may create IPC namespaces",
@ -1373,8 +1373,8 @@ static const struct security_assessor security_assessor_table[] = {
.parameter = CLONE_NEWIPC,
},
{
.id = "RestrictNamespaces=~CLONE_NEWPID",
.json_field = "RestrictNamespaces_CLONE_NEWPID",
.id = "RestrictNamespaces=~pid",
.json_field = "RestrictNamespaces_pid",
.url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
.description_good = "Service cannot create process namespaces",
.description_bad = "Service may create process namespaces",
@ -1384,8 +1384,8 @@ static const struct security_assessor security_assessor_table[] = {
.parameter = CLONE_NEWPID,
},
{
.id = "RestrictNamespaces=~CLONE_NEWCGROUP",
.json_field = "RestrictNamespaces_CLONE_NEWCGROUP",
.id = "RestrictNamespaces=~cgroup",
.json_field = "RestrictNamespaces_cgroup",
.url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
.description_good = "Service cannot create cgroup namespaces",
.description_bad = "Service may create cgroup namespaces",
@ -1395,8 +1395,8 @@ static const struct security_assessor security_assessor_table[] = {
.parameter = CLONE_NEWCGROUP,
},
{
.id = "RestrictNamespaces=~CLONE_NEWNET",
.json_field = "RestrictNamespaces_CLONE_NEWNET",
.id = "RestrictNamespaces=~net",
.json_field = "RestrictNamespaces_net",
.url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
.description_good = "Service cannot create network namespaces",
.description_bad = "Service may create network namespaces",
@ -1406,8 +1406,8 @@ static const struct security_assessor security_assessor_table[] = {
.parameter = CLONE_NEWNET,
},
{
.id = "RestrictNamespaces=~CLONE_NEWUTS",
.json_field = "RestrictNamespaces_CLONE_NEWUTS",
.id = "RestrictNamespaces=~uts",
.json_field = "RestrictNamespaces_uts",
.url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
.description_good = "Service cannot create hostname namespaces",
.description_bad = "Service may create hostname namespaces",
@ -1709,7 +1709,9 @@ static int assess(const SecurityInfo *info,
Table *overview_table,
AnalyzeSecurityFlags flags,
unsigned threshold,
JsonVariant *policy) {
JsonVariant *policy,
PagerFlags pager_flags,
JsonFormatFlags json_format_flags) {
static const struct {
uint64_t exposure;
@ -1732,15 +1734,19 @@ static int assess(const SecurityInfo *info,
int r;
if (!FLAGS_SET(flags, ANALYZE_SECURITY_SHORT)) {
details_table = table_new(" ", "name", "description", "weight", "badness", "range", "exposure");
details_table = table_new(" ", "name", "json_field", "description", "weight", "badness", "range", "exposure");
if (!details_table)
return log_oom();
r = table_set_json_field_name(details_table, 0, "set");
if (r < 0)
return log_error_errno(r, "Failed to set JSON field name of column 0: %m");
(void) table_set_sort(details_table, (size_t) 3, (size_t) 1);
(void) table_set_reverse(details_table, 3, true);
if (getenv_bool("SYSTEMD_ANALYZE_DEBUG") <= 0)
(void) table_set_display(details_table, (size_t) 0, (size_t) 1, (size_t) 2, (size_t) 6);
(void) table_set_display(details_table, (size_t) 0, (size_t) 1, (size_t) 2, (size_t) 3, (size_t) 7);
}
for (i = 0; i < ELEMENTSOF(security_assessor_table); i++) {
@ -1774,23 +1780,23 @@ static int assess(const SecurityInfo *info,
}
if (details_table) {
const char *checkmark, *description, *color = NULL;
const char *id = a->id;
const char *description, *color = NULL;
int checkmark;
if (badness == UINT64_MAX) {
checkmark = " ";
checkmark = -1;
description = access_description_na(a, policy);
color = NULL;
} else if (badness == a->range) {
checkmark = special_glyph(SPECIAL_GLYPH_CROSS_MARK);
checkmark = 0;
description = access_description_bad(a, policy);
color = ansi_highlight_red();
} else if (badness == 0) {
checkmark = special_glyph(SPECIAL_GLYPH_CHECK_MARK);
checkmark = 1;
description = access_description_good(a, policy);
color = ansi_highlight_green();
} else {
checkmark = special_glyph(SPECIAL_GLYPH_CROSS_MARK);
checkmark = 0;
description = NULL;
color = ansi_highlight_red();
}
@ -1798,16 +1804,24 @@ static int assess(const SecurityInfo *info,
if (d)
description = d;
if (json_variant_by_key(policy, a->json_field) != NULL)
id = a->json_field;
if (checkmark < 0) {
r = table_add_many(details_table, TABLE_EMPTY);
if (r < 0)
return table_log_add_error(r);
} else {
r = table_add_many(details_table,
TABLE_STRING, checkmark,
TABLE_BOOLEAN_CHECKMARK, checkmark > 0,
TABLE_SET_MINIMUM_WIDTH, 1,
TABLE_SET_MAXIMUM_WIDTH, 1,
TABLE_SET_ELLIPSIZE_PERCENT, 0,
TABLE_SET_COLOR, color,
TABLE_STRING, id, TABLE_SET_URL, a->url,
TABLE_SET_COLOR, color);
if (r < 0)
return table_log_add_error(r);
}
r = table_add_many(details_table,
TABLE_STRING, a->id, TABLE_SET_URL, a->url,
TABLE_STRING, a->json_field,
TABLE_STRING, description,
TABLE_UINT64, weight, TABLE_SET_ALIGN_PERCENT, 100,
TABLE_UINT64, badness, TABLE_SET_ALIGN_PERCENT, 100,
@ -1829,14 +1843,14 @@ static int assess(const SecurityInfo *info,
TableCell *cell;
uint64_t x;
assert_se(weight = table_get_at(details_table, row, 3));
assert_se(badness = table_get_at(details_table, row, 4));
assert_se(range = table_get_at(details_table, row, 5));
assert_se(weight = table_get_at(details_table, row, 4));
assert_se(badness = table_get_at(details_table, row, 5));
assert_se(range = table_get_at(details_table, row, 6));
if (*badness == UINT64_MAX || *badness == 0)
continue;
assert_se(cell = table_get_cell(details_table, row, 6));
assert_se(cell = table_get_cell(details_table, row, 7));
x = DIV_ROUND_UP(DIV_ROUND_UP(*badness * *weight * 100U, *range), weight_sum);
xsprintf(buf, "%" PRIu64 ".%" PRIu64, x / 10, x % 10);
@ -1846,7 +1860,13 @@ static int assess(const SecurityInfo *info,
return log_error_errno(r, "Failed to update cell in table: %m");
}
r = table_print(details_table, stdout);
if (json_format_flags & JSON_FORMAT_OFF) {
r = table_hide_column_from_display(details_table, (size_t) 2);
if (r < 0)
return log_error_errno(r, "Failed to set columns to display: %m");
}
r = table_print_with_pager(details_table, json_format_flags, pager_flags, /* show_header= */true);
if (r < 0)
return log_error_errno(r, "Failed to output table: %m");
}
@ -1859,7 +1879,7 @@ static int assess(const SecurityInfo *info,
assert(i < ELEMENTSOF(badness_table));
if (details_table) {
if (details_table && (json_format_flags & JSON_FORMAT_OFF)) {
_cleanup_free_ char *clickable = NULL;
const char *name;
@ -2386,7 +2406,9 @@ static int analyze_security_one(sd_bus *bus,
Table *overview_table,
AnalyzeSecurityFlags flags,
unsigned threshold,
JsonVariant *policy) {
JsonVariant *policy,
PagerFlags pager_flags,
JsonFormatFlags json_format_flags) {
_cleanup_(security_info_freep) SecurityInfo *info = security_info_new();
if (!info)
@ -2403,7 +2425,7 @@ static int analyze_security_one(sd_bus *bus,
if (r < 0)
return r;
r = assess(info, overview_table, flags, threshold, policy);
r = assess(info, overview_table, flags, threshold, policy, pager_flags, json_format_flags);
if (r < 0)
return r;
@ -2589,7 +2611,12 @@ static int get_security_info(Unit *u, ExecContext *c, CGroupContext *g, Security
return 0;
}
static int offline_security_check(Unit *u, unsigned threshold, JsonVariant *policy) {
static int offline_security_check(Unit *u,
unsigned threshold,
JsonVariant *policy,
PagerFlags pager_flags,
JsonFormatFlags json_format_flags) {
_cleanup_(table_unrefp) Table *overview_table = NULL;
AnalyzeSecurityFlags flags = 0;
_cleanup_(security_info_freep) SecurityInfo *info = NULL;
@ -2604,7 +2631,7 @@ static int offline_security_check(Unit *u, unsigned threshold, JsonVariant *poli
if (r < 0)
return r;
return assess(info, overview_table, flags, threshold, policy);
return assess(info, overview_table, flags, threshold, policy, pager_flags, json_format_flags);
}
static int offline_security_checks(char **filenames,
@ -2613,7 +2640,9 @@ static int offline_security_checks(char **filenames,
bool check_man,
bool run_generators,
unsigned threshold,
const char *root) {
const char *root,
PagerFlags pager_flags,
JsonFormatFlags json_format_flags) {
const ManagerTestRunFlags flags =
MANAGER_TEST_RUN_MINIMAL |
@ -2673,7 +2702,7 @@ static int offline_security_checks(char **filenames,
}
for (size_t i = 0; i < count; i++) {
k = offline_security_check(units[i], threshold, policy);
k = offline_security_check(units[i], threshold, policy, pager_flags, json_format_flags);
if (k < 0 && r == 0)
r = k;
}
@ -2690,6 +2719,8 @@ int analyze_security(sd_bus *bus,
bool offline,
unsigned threshold,
const char *root,
JsonFormatFlags json_format_flags,
PagerFlags pager_flags,
AnalyzeSecurityFlags flags) {
_cleanup_(table_unrefp) Table *overview_table = NULL;
@ -2698,7 +2729,7 @@ int analyze_security(sd_bus *bus,
assert(bus);
if (offline)
return offline_security_checks(units, policy, scope, check_man, run_generators, threshold, root);
return offline_security_checks(units, policy, scope, check_man, run_generators, threshold, root, pager_flags, json_format_flags);
if (strv_length(units) != 1) {
overview_table = table_new("unit", "exposure", "predicate", "happy");
@ -2758,7 +2789,7 @@ int analyze_security(sd_bus *bus,
flags |= ANALYZE_SECURITY_SHORT|ANALYZE_SECURITY_ONLY_LOADED|ANALYZE_SECURITY_ONLY_LONG_RUNNING;
STRV_FOREACH(i, list) {
r = analyze_security_one(bus, *i, overview_table, flags, threshold, policy);
r = analyze_security_one(bus, *i, overview_table, flags, threshold, policy, pager_flags, json_format_flags);
if (r < 0 && ret >= 0)
ret = r;
}
@ -2793,7 +2824,7 @@ int analyze_security(sd_bus *bus,
} else
name = mangled;
r = analyze_security_one(bus, name, overview_table, flags, threshold, policy);
r = analyze_security_one(bus, name, overview_table, flags, threshold, policy, pager_flags, json_format_flags);
if (r < 0 && ret >= 0)
ret = r;
}
@ -2805,10 +2836,9 @@ int analyze_security(sd_bus *bus,
fflush(stdout);
}
r = table_print(overview_table, stdout);
r = table_print_with_pager(overview_table, json_format_flags, pager_flags, /* show_header= */true);
if (r < 0)
return log_error_errno(r, "Failed to output table: %m");
}
return ret;
}

View File

@ -6,6 +6,7 @@
#include "sd-bus.h"
#include "json.h"
#include "pager.h"
#include "unit-file.h"
typedef enum AnalyzeSecurityFlags {
@ -23,4 +24,6 @@ int analyze_security(sd_bus *bus,
bool offline,
unsigned threshold,
const char *root,
JsonFormatFlags json_format_flags,
PagerFlags pager_flags,
AnalyzeSecurityFlags flags);

View File

@ -97,6 +97,7 @@ static unsigned arg_threshold = 100;
static unsigned arg_iterations = 1;
static usec_t arg_base_time = USEC_INFINITY;
static char *arg_unit = NULL;
static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
STATIC_DESTRUCTOR_REGISTER(arg_dot_from_patterns, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_dot_to_patterns, strv_freep);
@ -2196,6 +2197,8 @@ static int do_security(int argc, char *argv[], void *userdata) {
arg_offline,
arg_threshold,
arg_root,
arg_json_format_flags,
arg_pager_flags,
/*flags=*/ 0);
}
@ -2250,6 +2253,8 @@ static int help(int argc, char *argv[], void *userdata) {
" --version Show package version\n"
" --security-policy=PATH Use custom JSON security policy instead\n"
" of built-in one\n"
" --json=pretty|short|off Generate JSON output of the security\n"
" analysis table\n"
" --no-pager Do not pipe output into a pager\n"
" --system Operate on system systemd instance\n"
" --user Operate on user systemd instance\n"
@ -2303,6 +2308,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_OFFLINE,
ARG_THRESHOLD,
ARG_SECURITY_POLICY,
ARG_JSON,
};
static const struct option options[] = {
@ -2330,6 +2336,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "iterations", required_argument, NULL, ARG_ITERATIONS },
{ "base-time", required_argument, NULL, ARG_BASE_TIME },
{ "unit", required_argument, NULL, 'U' },
{ "json", required_argument, NULL, ARG_JSON },
{}
};
@ -2454,6 +2461,12 @@ static int parse_argv(int argc, char *argv[]) {
return r;
break;
case ARG_JSON:
r = parse_json_argument(optarg, &arg_json_format_flags);
if (r <= 0)
return r;
break;
case ARG_ITERATIONS:
r = safe_atou(optarg, &arg_iterations);
if (r < 0)
@ -2489,6 +2502,10 @@ static int parse_argv(int argc, char *argv[]) {
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Option --offline= is only supported for security right now.");
if (arg_json_format_flags != JSON_FORMAT_OFF && !streq_ptr(argv[optind], "security"))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Option --json= is only supported for security right now.");
if (arg_threshold != 100 && !streq_ptr(argv[optind], "security"))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Option --threshold= is only supported for security right now.");

View File

@ -80,4 +80,7 @@ ENV{ID_SOFTWARE_RADIO}=="?*", TAG+="uaccess"
# 3D printers, CNC machines, laser cutters, 3D scanners, etc.
ENV{ID_MAKER_TOOL}=="?*", TAG+="uaccess"
# Protocol analyzers
ENV{ID_SIGNAL_ANALYZER}=="?*", ENV{DEVTYPE}=="usb_device", TAG+="uaccess"
LABEL="uaccess_end"

View File

@ -11,8 +11,10 @@
#include "dirent-util.h"
#include "discover-image.h"
#include "dissect-image.h"
#include "env-file.h"
#include "errno-list.h"
#include "escape.h"
#include "extension-release.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
@ -230,6 +232,8 @@ DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(portable_metadata_hash_ops, char,
static int extract_now(
const char *where,
char **matches,
const char *image_name,
bool path_is_extension,
int socket_fd,
PortableMetadata **ret_os_release,
Hashmap **ret_unit_files) {
@ -239,6 +243,7 @@ static int extract_now(
_cleanup_(lookup_paths_free) LookupPaths paths = {};
_cleanup_close_ int os_release_fd = -1;
_cleanup_free_ char *os_release_path = NULL;
const char *os_release_id;
char **i;
int r;
@ -253,19 +258,27 @@ static int extract_now(
assert(where);
/* First, find /etc/os-release and send it upstream (or just save it). */
/* First, find os-release/extension-release and send it upstream (or just save it). */
if (path_is_extension) {
os_release_id = strjoina("/usr/lib/extension-release.d/extension-release.", image_name);
r = open_extension_release(where, image_name, &os_release_path, &os_release_fd);
} else {
os_release_id = "/etc/os-release";
r = open_os_release(where, &os_release_path, &os_release_fd);
}
if (r < 0)
log_debug_errno(r, "Couldn't acquire os-release file, ignoring: %m");
log_debug_errno(r,
"Couldn't acquire %s file, ignoring: %m",
path_is_extension ? "extension-release " : "os-release");
else {
if (socket_fd >= 0) {
r = send_item(socket_fd, "/etc/os-release", os_release_fd);
r = send_item(socket_fd, os_release_id, os_release_fd);
if (r < 0)
return log_debug_errno(r, "Failed to send os-release file: %m");
}
if (ret_os_release) {
os_release = portable_metadata_new("/etc/os-release", NULL, os_release_fd);
os_release = portable_metadata_new(os_release_id, NULL, os_release_fd);
if (!os_release)
return -ENOMEM;
@ -351,7 +364,7 @@ static int extract_now(
static int portable_extract_by_path(
const char *path,
bool extract_os_release,
bool path_is_extension,
char **matches,
PortableMetadata **ret_os_release,
Hashmap **ret_unit_files,
@ -369,7 +382,7 @@ static int portable_extract_by_path(
/* We can't turn this into a loop-back block device, and this returns EISDIR? Then this is a directory
* tree and not a raw device. It's easy then. */
r = extract_now(path, matches, -1, &os_release, &unit_files);
r = extract_now(path, matches, NULL, path_is_extension, -1, &os_release, &unit_files);
if (r < 0)
return r;
@ -423,15 +436,22 @@ static int portable_extract_by_path(
if (r < 0)
return r;
if (r == 0) {
DissectImageFlags flags = DISSECT_IMAGE_READ_ONLY;
seq[0] = safe_close(seq[0]);
r = dissected_image_mount(m, tmpdir, UID_INVALID, UID_INVALID, DISSECT_IMAGE_READ_ONLY);
if (path_is_extension)
flags |= DISSECT_IMAGE_VALIDATE_OS_EXT;
else
flags |= DISSECT_IMAGE_VALIDATE_OS;
r = dissected_image_mount(m, tmpdir, UID_INVALID, UID_INVALID, flags);
if (r < 0) {
log_debug_errno(r, "Failed to mount dissected image: %m");
goto child_finish;
}
r = extract_now(tmpdir, matches, seq[1], NULL, NULL);
r = extract_now(tmpdir, matches, m->image_name, path_is_extension, seq[1], NULL, NULL);
child_finish:
_exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
@ -477,7 +497,7 @@ static int portable_extract_by_path(
add = NULL;
} else if (PORTABLE_METADATA_IS_OS_RELEASE(add)) {
} else if (PORTABLE_METADATA_IS_OS_RELEASE(add) || PORTABLE_METADATA_IS_EXTENSION_RELEASE(add)) {
assert(!os_release);
os_release = TAKE_PTR(add);
@ -491,13 +511,12 @@ static int portable_extract_by_path(
child = 0;
}
/* When the portable image is layered, the image with units will not
* have a full filesystem, so no os-release - it will be in the root layer */
if (extract_os_release && !os_release)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image '%s' lacks os-release data, refusing.", path);
if (!extract_os_release && hashmap_isempty(unit_files))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Couldn't find any matching unit files in image '%s', refusing.", path);
if (!os_release)
return sd_bus_error_setf(error,
SD_BUS_ERROR_INVALID_ARGS,
"Image '%s' lacks %s data, refusing.",
path,
path_is_extension ? "extension-release" : "os-release");
if (ret_unit_files)
*ret_unit_files = TAKE_PTR(unit_files);
@ -508,14 +527,18 @@ static int portable_extract_by_path(
return 0;
}
int portable_extract(
static int extract_image_and_extensions(
const char *name_or_path,
char **matches,
char **extension_image_paths,
bool validate_sysext,
Image **ret_image,
OrderedHashmap **ret_extension_images,
PortableMetadata **ret_os_release,
Hashmap **ret_unit_files,
sd_bus_error *error) {
_cleanup_free_ char *id = NULL, *version_id = NULL, *sysext_level = NULL;
_cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL;
_cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL;
_cleanup_hashmap_free_ Hashmap *unit_files = NULL;
@ -524,6 +547,9 @@ int portable_extract(
int r;
assert(name_or_path);
assert(matches);
assert(ret_image);
assert(ret_extension_images);
r = image_find_harder(IMAGE_PORTABLE, name_or_path, NULL, &image);
if (r < 0)
@ -550,19 +576,106 @@ int portable_extract(
}
}
r = portable_extract_by_path(image->path, true, matches, &os_release, &unit_files, error);
r = portable_extract_by_path(image->path, /* path_is_extension= */ false, matches, &os_release, &unit_files, error);
if (r < 0)
return r;
/* If we are layering extension images on top of a runtime image, check that the os-release and extension-release metadata
* match, otherwise reject it immediately as invalid, or it will fail when the units are started. */
if (validate_sysext) {
_cleanup_fclose_ FILE *f = NULL;
r = take_fdopen_unlocked(&os_release->fd, "r", &f);
if (r < 0)
return r;
r = parse_env_file(f, os_release->name,
"ID", &id,
"VERSION_ID", &version_id,
"SYSEXT_LEVEL", &sysext_level);
if (r < 0)
return r;
}
ORDERED_HASHMAP_FOREACH(ext, extension_images) {
_cleanup_(portable_metadata_unrefp) PortableMetadata *extension_release_meta = NULL;
_cleanup_hashmap_free_ Hashmap *extra_unit_files = NULL;
_cleanup_strv_free_ char **extension_release = NULL;
_cleanup_fclose_ FILE *f = NULL;
r = portable_extract_by_path(ext->path, false, matches, NULL, &extra_unit_files, error);
r = portable_extract_by_path(ext->path, /* path_is_extension= */ true, matches, &extension_release_meta, &extra_unit_files, error);
if (r < 0)
return r;
r = hashmap_move(unit_files, extra_unit_files);
if (r < 0)
return r;
if (!validate_sysext)
continue;
r = take_fdopen_unlocked(&extension_release_meta->fd, "r", &f);
if (r < 0)
return r;
r = load_env_file_pairs(f, extension_release_meta->name, &extension_release);
if (r < 0)
return r;
r = extension_release_validate(ext->path, id, version_id, sysext_level, extension_release);
if (r == 0)
return sd_bus_error_set_errnof(error, SYNTHETIC_ERRNO(ESTALE), "Image %s extension-release metadata does not match the root's", ext->path);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to compare image %s extension-release metadata with the root's os-release: %m", ext->path);
}
*ret_image = TAKE_PTR(image);
*ret_extension_images = TAKE_PTR(extension_images);
if (ret_os_release)
*ret_os_release = TAKE_PTR(os_release);
if (ret_unit_files)
*ret_unit_files = TAKE_PTR(unit_files);
return 0;
}
int portable_extract(
const char *name_or_path,
char **matches,
char **extension_image_paths,
PortableMetadata **ret_os_release,
Hashmap **ret_unit_files,
sd_bus_error *error) {
_cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL;
_cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL;
_cleanup_hashmap_free_ Hashmap *unit_files = NULL;
_cleanup_(image_unrefp) Image *image = NULL;
int r;
r = extract_image_and_extensions(name_or_path,
matches,
extension_image_paths,
/* validate_sysext= */ false,
&image,
&extension_images,
&os_release,
&unit_files,
error);
if (r < 0)
return r;
if (hashmap_isempty(unit_files)) {
_cleanup_free_ char *extensions = strv_join(extension_image_paths, ", ");
if (!extensions)
return -ENOMEM;
return sd_bus_error_setf(error,
SD_BUS_ERROR_INVALID_ARGS,
"Couldn't find any matching unit files in image '%s%s%s', refusing.",
image->path,
isempty(extensions) ? "" : "' or any of its extensions '",
isempty(extensions) ? "" : extensions);
}
*ret_os_release = TAKE_PTR(os_release);
@ -1138,47 +1251,31 @@ int portable_attach(
_cleanup_(lookup_paths_free) LookupPaths paths = {};
_cleanup_(image_unrefp) Image *image = NULL;
PortableMetadata *item;
Image *ext;
char **p;
int r;
assert(name_or_path);
r = image_find_harder(IMAGE_PORTABLE, name_or_path, NULL, &image);
r = extract_image_and_extensions(name_or_path,
matches,
extension_image_paths,
/* validate_sysext= */ true,
&image,
&extension_images,
/* os_release= */ NULL,
&unit_files,
error);
if (r < 0)
return r;
if (!strv_isempty(extension_image_paths)) {
extension_images = ordered_hashmap_new(&image_hash_ops);
if (!extension_images)
if (hashmap_isempty(unit_files)) {
_cleanup_free_ char *extensions = strv_join(extension_image_paths, ", ");
if (!extensions)
return -ENOMEM;
STRV_FOREACH(p, extension_image_paths) {
_cleanup_(image_unrefp) Image *new = NULL;
r = image_find_harder(IMAGE_PORTABLE, *p, NULL, &new);
if (r < 0)
return r;
r = ordered_hashmap_put(extension_images, new->name, new);
if (r < 0)
return r;
TAKE_PTR(new);
}
}
r = portable_extract_by_path(image->path, true, matches, NULL, &unit_files, error);
if (r < 0)
return r;
ORDERED_HASHMAP_FOREACH(ext, extension_images) {
_cleanup_hashmap_free_ Hashmap *extra_unit_files = NULL;
r = portable_extract_by_path(ext->path, false, matches, NULL, &extra_unit_files, error);
if (r < 0)
return r;
r = hashmap_move(unit_files, extra_unit_files);
if (r < 0)
return r;
return sd_bus_error_setf(error,
SD_BUS_ERROR_INVALID_ARGS,
"Couldn't find any matching unit files in image '%s%s%s', refusing.",
image->path,
isempty(extensions) ? "" : "' or any of its extensions '",
isempty(extensions) ? "" : extensions);
}
r = lookup_paths_init(&paths, UNIT_FILE_SYSTEM, LOOKUP_PATHS_SPLIT_USR, NULL);

View File

@ -16,6 +16,7 @@ typedef struct PortableMetadata {
} PortableMetadata;
#define PORTABLE_METADATA_IS_OS_RELEASE(m) (streq((m)->name, "/etc/os-release"))
#define PORTABLE_METADATA_IS_EXTENSION_RELEASE(m) (startswith((m)->name, "/usr/lib/extension-release.d/extension-release."))
#define PORTABLE_METADATA_IS_UNIT(m) (!IN_SET((m)->name[0], 0, '/'))
typedef enum PortableFlags {

View File

@ -1742,17 +1742,28 @@ int dissected_image_mount(
if (r < 0)
return r;
if (flags & DISSECT_IMAGE_VALIDATE_OS) {
if ((flags & (DISSECT_IMAGE_VALIDATE_OS|DISSECT_IMAGE_VALIDATE_OS_EXT)) != 0) {
/* If either one of the validation flags are set, ensure that the image qualifies
* as one or the other (or both). */
bool ok = false;
if (FLAGS_SET(flags, DISSECT_IMAGE_VALIDATE_OS)) {
r = path_is_os_tree(where);
if (r < 0)
return r;
if (r == 0) {
if (r > 0)
ok = true;
}
if (!ok && FLAGS_SET(flags, DISSECT_IMAGE_VALIDATE_OS_EXT)) {
r = path_is_extension_tree(where, m->image_name);
if (r < 0)
return r;
if (r == 0)
return -EMEDIUMTYPE;
if (r > 0)
ok = true;
}
if (!ok)
return -ENOMEDIUM;
}
}
@ -2623,6 +2634,7 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
DISSECT_IMAGE_READ_ONLY|
DISSECT_IMAGE_MOUNT_ROOT_ONLY|
DISSECT_IMAGE_VALIDATE_OS|
DISSECT_IMAGE_VALIDATE_OS_EXT|
DISSECT_IMAGE_USR_NO_ROOT);
if (r < 0) {
/* Let parent know the error */

View File

@ -100,19 +100,20 @@ typedef enum DissectImageFlags {
DISSECT_IMAGE_MOUNT_ROOT_ONLY = 1 << 6, /* Mount only the root and /usr partitions */
DISSECT_IMAGE_MOUNT_NON_ROOT_ONLY = 1 << 7, /* Mount only the non-root and non-/usr partitions */
DISSECT_IMAGE_VALIDATE_OS = 1 << 8, /* Refuse mounting images that aren't identifiable as OS images */
DISSECT_IMAGE_NO_UDEV = 1 << 9, /* Don't wait for udev initializing things */
DISSECT_IMAGE_RELAX_VAR_CHECK = 1 << 10, /* Don't insist that the UUID of /var is hashed from /etc/machine-id */
DISSECT_IMAGE_FSCK = 1 << 11, /* File system check the partition before mounting (no effect when combined with DISSECT_IMAGE_READ_ONLY) */
DISSECT_IMAGE_NO_PARTITION_TABLE = 1 << 12, /* Only recognize single file system images */
DISSECT_IMAGE_VERITY_SHARE = 1 << 13, /* When activating a verity device, reuse existing one if already open */
DISSECT_IMAGE_MKDIR = 1 << 14, /* Make top-level directory to mount right before mounting, if missing */
DISSECT_IMAGE_USR_NO_ROOT = 1 << 15, /* If no root fs is in the image, but /usr is, then allow this (so that we can mount the rootfs as tmpfs or so */
DISSECT_IMAGE_REQUIRE_ROOT = 1 << 16, /* Don't accept disks without root partition (or at least /usr partition if DISSECT_IMAGE_USR_NO_ROOT is set) */
DISSECT_IMAGE_MOUNT_READ_ONLY = 1 << 17, /* Make mounts read-only */
DISSECT_IMAGE_VALIDATE_OS_EXT = 1 << 9, /* Refuse mounting images that aren't identifiable as OS extension images */
DISSECT_IMAGE_NO_UDEV = 1 << 10, /* Don't wait for udev initializing things */
DISSECT_IMAGE_RELAX_VAR_CHECK = 1 << 11, /* Don't insist that the UUID of /var is hashed from /etc/machine-id */
DISSECT_IMAGE_FSCK = 1 << 12, /* File system check the partition before mounting (no effect when combined with DISSECT_IMAGE_READ_ONLY) */
DISSECT_IMAGE_NO_PARTITION_TABLE = 1 << 13, /* Only recognize single file system images */
DISSECT_IMAGE_VERITY_SHARE = 1 << 14, /* When activating a verity device, reuse existing one if already open */
DISSECT_IMAGE_MKDIR = 1 << 15, /* Make top-level directory to mount right before mounting, if missing */
DISSECT_IMAGE_USR_NO_ROOT = 1 << 16, /* If no root fs is in the image, but /usr is, then allow this (so that we can mount the rootfs as tmpfs or so */
DISSECT_IMAGE_REQUIRE_ROOT = 1 << 17, /* Don't accept disks without root partition (or at least /usr partition if DISSECT_IMAGE_USR_NO_ROOT is set) */
DISSECT_IMAGE_MOUNT_READ_ONLY = 1 << 18, /* Make mounts read-only */
DISSECT_IMAGE_READ_ONLY = DISSECT_IMAGE_DEVICE_READ_ONLY |
DISSECT_IMAGE_MOUNT_READ_ONLY,
DISSECT_IMAGE_GROWFS = 1 << 18, /* Grow file systems in partitions marked for that to the size of the partitions after mount */
DISSECT_IMAGE_MOUNT_IDMAPPED = 1 << 19, /* Mount mounts with kernel 5.12-style userns ID mapping, if file system type doesn't support uid=/gid= */
DISSECT_IMAGE_GROWFS = 1 << 19, /* Grow file systems in partitions marked for that to the size of the partitions after mount */
DISSECT_IMAGE_MOUNT_IDMAPPED = 1 << 20, /* Mount mounts with kernel 5.12-style userns ID mapping, if file system type doesn't support uid=/gid= */
} DissectImageFlags;
struct DissectedImage {

View File

@ -267,6 +267,7 @@ static size_t table_data_size(TableDataType type, const void *data) {
case TABLE_STRV_WRAPPED:
return sizeof(char **);
case TABLE_BOOLEAN_CHECKMARK:
case TABLE_BOOLEAN:
return sizeof(bool);
@ -848,6 +849,7 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) {
data = va_arg(ap, char * const *);
break;
case TABLE_BOOLEAN_CHECKMARK:
case TABLE_BOOLEAN:
buffer.b = va_arg(ap, int);
data = &buffer.b;
@ -1443,6 +1445,9 @@ static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercas
case TABLE_BOOLEAN:
return yes_no(d->boolean);
case TABLE_BOOLEAN_CHECKMARK:
return special_glyph(d->boolean ? SPECIAL_GLYPH_CHECK_MARK : SPECIAL_GLYPH_CROSS_MARK);
case TABLE_TIMESTAMP:
case TABLE_TIMESTAMP_UTC:
case TABLE_TIMESTAMP_RELATIVE: {
@ -2488,6 +2493,7 @@ static int table_data_to_json(TableData *d, JsonVariant **ret) {
case TABLE_STRV_WRAPPED:
return json_variant_new_array_strv(ret, d->strv);
case TABLE_BOOLEAN_CHECKMARK:
case TABLE_BOOLEAN:
return json_variant_new_boolean(ret, d->boolean);

View File

@ -16,6 +16,7 @@ typedef enum TableDataType {
TABLE_STRV_WRAPPED,
TABLE_PATH,
TABLE_BOOLEAN,
TABLE_BOOLEAN_CHECKMARK,
TABLE_TIMESTAMP,
TABLE_TIMESTAMP_UTC,
TABLE_TIMESTAMP_RELATIVE,

View File

@ -412,43 +412,43 @@ cat <<EOF >/tmp/testfile.json
"weight": 1000,
"range": 1
},
"RestrictNamespaces_CLONE_NEWUSER":
"RestrictNamespaces_user":
{"description_good": "Servicecannotcreateusernamespaces",
"description_bad": "Servicemaycreateusernamespaces",
"weight": 1500,
"range": 1
},
"RestrictNamespaces_CLONE_NEWNS":
"RestrictNamespaces_mnt":
{"description_good": "Service cannot create file system namespaces",
"description_bad": "Service may create file system namespaces",
"weight": 500,
"range": 1
},
"RestrictNamespaces_CLONE_NEWIPC":
"RestrictNamespaces_ipc":
{"description_good": "Service cannot create IPC namespaces",
"description_bad": "Service may create IPC namespaces",
"weight": 500,
"range": 1
},
"RestrictNamespaces_CLONE_NEWPID":
"RestrictNamespaces_pid":
{"description_good": "Service cannot create process namespaces",
"description_bad": "Service may create process namespaces",
"weight": 500,
"range": 1
},
"RestrictNamespaces_CLONE_NEWCGROUP":
"RestrictNamespaces_cgroup":
{"description_good": "Service cannot create cgroup namespaces",
"description_bad": "Service may create cgroup namespaces",
"weight": 500,
"range": 1
},
"RestrictNamespaces_CLONE_NEWNET":
"RestrictNamespaces_net":
{"description_good": "Service cannot create network namespaces",
"description_bad": "Service may create network namespaces",
"weight": 500,
"range": 1
},
"RestrictNamespaces_CLONE_NEWUTS":
"RestrictNamespaces_uts":
{"description_good": "Service cannot create hostname namespaces",
"description_bad": "Service may create hostname namespaces",
"weight": 100,