Coverage Report

Created: 2026-05-04 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/systemd/src/shared/extension-util.c
Line
Count
Source
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3
#include "alloc-util.h"
4
#include "architecture.h"
5
#include "chase.h"
6
#include "env-util.h"
7
#include "extension-util.h"
8
#include "log.h"
9
#include "os-util.h"
10
#include "string-util.h"
11
#include "strv.h"
12
13
int extension_release_validate(
14
                const char *name,
15
                const char *host_os_release_id,
16
                const char *host_os_release_id_like,
17
                const char *host_os_release_version_id,
18
                const char *host_os_extension_release_level,
19
                const char *host_extension_scope,
20
                char **extension_release,
21
0
                ImageClass image_class) {
22
23
0
        const char *extension_release_id = NULL, *extension_release_level = NULL, *extension_architecture = NULL;
24
0
        const char *extension_level = image_class == IMAGE_CONFEXT ? "CONFEXT_LEVEL" : "SYSEXT_LEVEL";
25
0
        const char *extension_scope = image_class == IMAGE_CONFEXT ? "CONFEXT_SCOPE" : "SYSEXT_SCOPE";
26
0
        _cleanup_strv_free_ char **id_like_l = NULL;
27
28
0
        assert(name);
29
0
        assert(!isempty(host_os_release_id));
30
31
        /* Now that we can look into the extension/confext image, let's see if the OS version is compatible */
32
0
        if (strv_isempty(extension_release)) {
33
0
                log_debug("Extension '%s' carries no release data, ignoring.", name);
34
0
                return 0;
35
0
        }
36
37
0
        if (host_extension_scope) {
38
0
                _cleanup_strv_free_ char **scope_list = NULL;
39
0
                const char *scope;
40
0
                bool valid;
41
42
0
                scope = strv_env_pairs_get(extension_release, extension_scope);
43
0
                if (scope) {
44
0
                        scope_list = strv_split(scope, WHITESPACE);
45
0
                        if (!scope_list)
46
0
                                return -ENOMEM;
47
0
                }
48
49
                /* By default extension are good for attachment in portable service and on the system */
50
0
                valid = strv_contains(
51
0
                        scope_list ?: STRV_MAKE("system", "portable"),
52
0
                        host_extension_scope);
53
0
                if (!valid) {
54
0
                        log_debug("Extension '%s' is not suitable for scope %s, ignoring.", name, host_extension_scope);
55
0
                        return 0;
56
0
                }
57
0
        }
58
59
        /* When the architecture field is present and not '_any' it must match the host - for now just look at uname but in
60
         * the future we could check if the kernel also supports 32 bit or binfmt has a translator set up for the architecture */
61
0
        extension_architecture = strv_env_pairs_get(extension_release, "ARCHITECTURE");
62
0
        if (!isempty(extension_architecture) && !streq(extension_architecture, "_any") &&
63
0
        !streq(architecture_to_string(uname_architecture()), extension_architecture)) {
64
0
                log_debug("Extension '%s' is for architecture '%s', but deployed on top of '%s'.",
65
0
                        name, extension_architecture, architecture_to_string(uname_architecture()));
66
0
                return 0;
67
0
        }
68
69
0
        extension_release_id = strv_env_pairs_get(extension_release, "ID");
70
0
        if (isempty(extension_release_id)) {
71
0
                log_debug("Extension '%s' does not contain ID in release file but requested to match '%s' or be '_any'",
72
0
                        name, host_os_release_id);
73
0
                return 0;
74
0
        }
75
76
        /* A sysext(or confext) with no host OS dependency (static binaries or scripts) can match
77
         * '_any' host OS, and VERSION_ID or SYSEXT_LEVEL(or CONFEXT_LEVEL) are not required anywhere */
78
0
        if (streq(extension_release_id, "_any")) {
79
0
                log_debug("Extension '%s' matches '_any' OS.", name);
80
0
                return 1;
81
0
        }
82
83
        /* Match extension OS ID against host OS ID or ID_LIKE */
84
0
        if (host_os_release_id_like) {
85
0
                id_like_l = strv_split(host_os_release_id_like, WHITESPACE);
86
0
                if (!id_like_l)
87
0
                        return log_oom();
88
0
        }
89
90
0
        if (!streq(host_os_release_id, extension_release_id) && !strv_contains(id_like_l, extension_release_id)) {
91
0
                log_debug("Extension '%s' is for OS '%s', but deployed on top of '%s'%s%s%s.",
92
0
                          name, extension_release_id, host_os_release_id,
93
0
                          host_os_release_id_like ? " (like '" : "",
94
0
                          strempty(host_os_release_id_like),
95
0
                          host_os_release_id_like ? "')" : "");
96
0
                return 0;
97
0
        }
98
99
        /* Rolling releases do not typically set VERSION_ID (eg: ArchLinux) */
100
0
        if (isempty(host_os_release_version_id) && isempty(host_os_extension_release_level)) {
101
0
                log_debug("No version info on the host (rolling release?), but ID in %s matched.", name);
102
0
                return 1;
103
0
        }
104
105
        /* If the extension has a sysext API level declared, then it must match the host API
106
         * level. Otherwise, compare OS version as a whole */
107
0
        extension_release_level = strv_env_pairs_get(extension_release, extension_level);
108
0
        if (!isempty(host_os_extension_release_level) && !isempty(extension_release_level)) {
109
0
                if (!streq_ptr(host_os_extension_release_level, extension_release_level)) {
110
0
                        log_debug("Extension '%s' is for API level '%s', but running on API level '%s'",
111
0
                                name, strna(extension_release_level), strna(host_os_extension_release_level));
112
0
                        return 0;
113
0
                }
114
0
        } else if (!isempty(host_os_release_version_id)) {
115
0
                const char *extension_release_version_id;
116
117
0
                extension_release_version_id = strv_env_pairs_get(extension_release, "VERSION_ID");
118
0
                if (isempty(extension_release_version_id)) {
119
0
                        log_debug("Extension '%s' does not contain VERSION_ID in release file but requested to match '%s'",
120
0
                                  name, strna(host_os_release_version_id));
121
0
                        return 0;
122
0
                }
123
124
0
                if (!streq_ptr(host_os_release_version_id, extension_release_version_id)) {
125
0
                        log_debug("Extension '%s' is for OS '%s', but deployed on top of '%s'.",
126
0
                                  name, strna(extension_release_version_id), strna(host_os_release_version_id));
127
0
                        return 0;
128
0
                }
129
0
        } else if (isempty(host_os_release_version_id) && isempty(host_os_extension_release_level)) {
130
                /* Rolling releases do not typically set VERSION_ID (eg: ArchLinux) */
131
0
                log_debug("No version info on the host (rolling release?), but ID in %s matched.", name);
132
0
                return 1;
133
0
        }
134
135
0
        log_debug("Version info of extension '%s' matches host.", name);
136
0
        return 1;
137
0
}
138
139
0
int parse_env_extension_hierarchies(char ***ret_hierarchies, const char *hierarchy_env) {
140
0
        _cleanup_free_ char **l = NULL;
141
0
        int r;
142
143
0
        assert(ret_hierarchies);
144
0
        assert(hierarchy_env);
145
0
        r = getenv_path_list(hierarchy_env, &l);
146
0
        if (r == -ENXIO) {
147
0
                if (streq(hierarchy_env, "SYSTEMD_CONFEXT_HIERARCHIES"))
148
                        /* Default for confext when unset */
149
0
                        l = strv_new("/etc");
150
0
                else if (streq(hierarchy_env, "SYSTEMD_SYSEXT_HIERARCHIES"))
151
                        /* Default for sysext when unset */
152
0
                        l = strv_new("/usr", "/opt");
153
0
                else if (streq(hierarchy_env, "SYSTEMD_SYSEXT_AND_CONFEXT_HIERARCHIES"))
154
                        /* Combined sysext and confext directories */
155
0
                        l = strv_new("/usr", "/opt", "/etc");
156
0
                else
157
0
                        return -ENXIO;
158
0
        } else if (r < 0)
159
0
                return r;
160
161
0
        *ret_hierarchies = TAKE_PTR(l);
162
0
        return 0;
163
0
}
164
165
0
int extension_has_forbidden_content(const char *root) {
166
0
        int r;
167
168
        /* Insist that extension images do not overwrite the underlying OS release file (it's fine if
169
         * they place one in /etc/os-release, i.e. where things don't matter, as they aren't
170
         * merged.) */
171
0
        r = chase("/usr/lib/os-release", root, CHASE_PREFIX_ROOT, NULL, NULL);
172
0
        if (r > 0) {
173
0
                log_debug("Extension contains '/usr/lib/os-release', which is not allowed, refusing.");
174
0
                return 1;
175
0
        }
176
0
        if (r < 0 && r != -ENOENT)
177
0
                return log_debug_errno(r, "Failed to determine whether '/usr/lib/os-release' exists in the extension: %m");
178
179
0
        return 0;
180
0
}