summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJinfeng Wang <jinfeng.wang.cn@windriver.com>2025-04-14 15:03:36 +0800
committerArmin Kuster <akuster808@gmail.com>2025-04-20 13:43:06 -0400
commit77e91fceec324a17be4c186207000332f4f9c849 (patch)
tree05020aa806f28f80c4cca98461ee5df43c98d912
parent99aa00f759b1bd36c00886babc720041a7ac0f78 (diff)
downloadmeta-openembedded-77e91fceec324a17be4c186207000332f4f9c849.tar.gz
netplan: Fix CVE-2022-4968
Backport patch[1] to fix CVE-2022-4968. [1] https://github.com/canonical/netplan/commit/4c39b75b5c6ae7d976bda6da68da60d9a7f085ee Signed-off-by: Jinfeng Wang <jinfeng.wang.cn@windriver.com>
-rw-r--r--meta-networking/recipes-connectivity/netplan/netplan/CVE-2022-4968.patch442
-rw-r--r--meta-networking/recipes-connectivity/netplan/netplan_0.104.bb1
2 files changed, 443 insertions, 0 deletions
diff --git a/meta-networking/recipes-connectivity/netplan/netplan/CVE-2022-4968.patch b/meta-networking/recipes-connectivity/netplan/netplan/CVE-2022-4968.patch
new file mode 100644
index 0000000000..0942309cb1
--- /dev/null
+++ b/meta-networking/recipes-connectivity/netplan/netplan/CVE-2022-4968.patch
@@ -0,0 +1,442 @@
1From 78bfd2429a64452cb8023ec1a56837874c641f5f Mon Sep 17 00:00:00 2001
2From: Danilo Egea Gondolfo <danilogondolfo@gmail.com>
3Date: Wed, 22 May 2024 15:44:16 +0100
4Subject: [PATCH] libnetplan: use more restrictive file permissions
5
6A new util.c:_netplan_g_string_free_to_file_with_permissions() was added
7and accepts the owner, group and file mode as arguments. When these
8properties can't be set, when the generator is called by a non-root user
9for example, it will not hard-fail. This function is called by unit
10tests where we can't set the owner to a privileged account for example.
11
12When generating backend files, use more restrictive permissions:
13
14networkd related files will be owned by root:systemd-network and have
15mode 0640.
16
17service unit files will be owned by root:root and have mode 0640.
18udevd files will be owned by root:root with mode 0640.
19
20wpa_supplicant and Network Manager files will continue with the existing
21permissions.
22
23Autopkgtests will check if the permissions are set as expected when
24calling the generator.
25
26This fix addresses CVE-2022-4968
27
28Fix conflicts according to:
29https://git.launchpad.net/~ubuntu-core-dev/netplan/+git/ubuntu/diff/debian/patches/lp2065738/0013-libnetplan-use-more-restrictive-file-permissions.patch?id=ed684b8a3eb282b9bc7c0f18ad6b2249e7f3ef30
30
31CVE: CVE-2022-4968
32
33Upstream-Status: Backport [https://github.com/canonical/netplan/commit/4c39b75b5c6ae7d976bda6da68da60d9a7f085ee]
34
35Signed-off-by: Jinfeng Wang <jinfeng.wang.cn@windriver.com>
36
37update
38---
39 src/networkd.c | 34 +++-----------
40 src/networkd.h | 2 +
41 src/nm.c | 4 +-
42 src/openvswitch.c | 2 +-
43 src/sriov.c | 2 +-
44 src/util-internal.h | 3 ++
45 src/util.c | 46 +++++++++++++++++++
46 tests/generator/test_auth.py | 2 +-
47 tests/generator/test_wifis.py | 2 +-
48 tests/integration/base.py | 86 +++++++++++++++++++++++++++++++++++
49 10 files changed, 149 insertions(+), 34 deletions(-)
50
51diff --git a/src/networkd.c b/src/networkd.c
52index 6d26047c..56e6e6ed 100644
53--- a/src/networkd.c
54+++ b/src/networkd.c
55@@ -231,7 +231,6 @@ static void
56 write_link_file(const NetplanNetDefinition* def, const char* rootdir, const char* path)
57 {
58 GString* s = NULL;
59- mode_t orig_umask;
60
61 /* Don't write .link files for virtual devices; they use .netdev instead.
62 * Don't write .link files for MODEM devices, as they aren't supported by networkd.
63@@ -286,9 +285,7 @@ write_link_file(const NetplanNetDefinition* def, const char* rootdir, const char
64 if (def->large_receive_offload)
65 g_string_append_printf(s, "LargeReceiveOffload=%u\n", def->large_receive_offload);
66
67- orig_umask = umask(022);
68- g_string_free_to_file(s, rootdir, path, ".link");
69- umask(orig_umask);
70+ _netplan_g_string_free_to_file_with_permissions(s, rootdir, path, ".link", "root", "root", 0640);
71 }
72
73
74@@ -387,7 +384,6 @@ static void
75 write_netdev_file(const NetplanNetDefinition* def, const char* rootdir, const char* path)
76 {
77 GString* s = NULL;
78- mode_t orig_umask;
79
80 g_assert(def->type >= NETPLAN_DEF_TYPE_VIRTUAL);
81
82@@ -454,11 +450,7 @@ write_netdev_file(const NetplanNetDefinition* def, const char* rootdir, const ch
83 default: g_assert_not_reached(); // LCOV_EXCL_LINE
84 }
85
86- /* these do not contain secrets and need to be readable by
87- * systemd-networkd - LP: #1736965 */
88- orig_umask = umask(022);
89- g_string_free_to_file(s, rootdir, path, ".netdev");
90- umask(orig_umask);
91+ _netplan_g_string_free_to_file_with_permissions(s, rootdir, path, ".netdev", "root", NETWORKD_GROUP, 0640);
92 }
93
94 static void
95@@ -602,7 +594,6 @@ netplan_netdef_write_network_file(
96 GString* network = NULL;
97 GString* link = NULL;
98 GString* s = NULL;
99- mode_t orig_umask;
100 gboolean is_optional = def->optional;
101
102 SET_OPT_OUT_PTR(has_been_written, FALSE);
103@@ -827,11 +818,7 @@ netplan_netdef_write_network_file(
104 g_string_free(link, TRUE);
105 g_string_free(network, TRUE);
106
107- /* these do not contain secrets and need to be readable by
108- * systemd-networkd - LP: #1736965 */
109- orig_umask = umask(022);
110- g_string_free_to_file(s, rootdir, path, ".network");
111- umask(orig_umask);
112+ _netplan_g_string_free_to_file_with_permissions(s, rootdir, path, ".network", "root", NETWORKD_GROUP, 0640);
113 }
114
115 SET_OPT_OUT_PTR(has_been_written, TRUE);
116@@ -843,7 +830,6 @@ write_rules_file(const NetplanNetDefinition* def, const char* rootdir)
117 {
118 GString* s = NULL;
119 g_autofree char* path = g_strjoin(NULL, "run/udev/rules.d/99-netplan-", def->id, ".rules", NULL);
120- mode_t orig_umask;
121
122 /* do we need to write a .rules file?
123 * It's only required for reliably setting the name of a physical device
124@@ -877,9 +863,7 @@ write_rules_file(const NetplanNetDefinition* def, const char* rootdir)
125
126 g_string_append_printf(s, "NAME=\"%s\"\n", def->set_name);
127
128- orig_umask = umask(022);
129- g_string_free_to_file(s, rootdir, path, NULL);
130- umask(orig_umask);
131+ _netplan_g_string_free_to_file_with_permissions(s, rootdir, path, NULL, "root", "root", 0640);
132 }
133
134 static gboolean
135@@ -981,7 +965,6 @@ static void
136 write_wpa_unit(const NetplanNetDefinition* def, const char* rootdir)
137 {
138 g_autofree gchar *stdouth = NULL;
139- mode_t orig_umask;
140
141 stdouth = systemd_escape(def->id);
142
143@@ -1000,9 +983,7 @@ write_wpa_unit(const NetplanNetDefinition* def, const char* rootdir)
144 } else {
145 g_string_append(s, " -Dnl80211,wext\n");
146 }
147- orig_umask = umask(022);
148- g_string_free_to_file(s, rootdir, path, NULL);
149- umask(orig_umask);
150+ _netplan_g_string_free_to_file_with_permissions(s, rootdir, path, NULL, "root", "root", 0640);
151 }
152
153 static gboolean
154@@ -1011,7 +992,6 @@ write_wpa_conf(const NetplanNetDefinition* def, const char* rootdir, GError** er
155 GHashTableIter iter;
156 GString* s = g_string_new("ctrl_interface=/run/wpa_supplicant\n\n");
157 g_autofree char* path = g_strjoin(NULL, "run/netplan/wpa-", def->id, ".conf", NULL);
158- mode_t orig_umask;
159
160 g_debug("%s: Creating wpa_supplicant configuration file %s", def->id, path);
161 if (def->type == NETPLAN_DEF_TYPE_WIFI) {
162@@ -1087,9 +1067,7 @@ write_wpa_conf(const NetplanNetDefinition* def, const char* rootdir, GError** er
163 }
164
165 /* use tight permissions as this contains secrets */
166- orig_umask = umask(077);
167- g_string_free_to_file(s, rootdir, path, NULL);
168- umask(orig_umask);
169+ _netplan_g_string_free_to_file_with_permissions(s, rootdir, path, NULL, "root", "root", 0600);
170 return TRUE;
171 }
172
173diff --git a/src/networkd.h b/src/networkd.h
174index a7092b2c..0214e43b 100644
175--- a/src/networkd.h
176+++ b/src/networkd.h
177@@ -20,6 +20,8 @@
178 #include "netplan.h"
179 #include <glib.h>
180
181+#define NETWORKD_GROUP "systemd-network"
182+
183 NETPLAN_INTERNAL gboolean
184 netplan_netdef_write_networkd(
185 const NetplanState* np_state,
186diff --git a/src/nm.c b/src/nm.c
187index 319a80ba..7770a574 100644
188--- a/src/nm.c
189+++ b/src/nm.c
190@@ -996,13 +996,13 @@ netplan_state_finish_nm_write(
191 len = s->len;
192 g_list_foreach(np_state->netdefs_ordered, nd_append_non_nm_ids, s);
193 if (s->len > len)
194- g_string_free_to_file(s, rootdir, "run/NetworkManager/conf.d/netplan.conf", NULL);
195+ _netplan_g_string_free_to_file_with_permissions(s, rootdir, "run/NetworkManager/conf.d/netplan.conf", NULL, "root", "root", 0640);
196 else
197 g_string_free(s, TRUE);
198
199 /* write generated udev rules */
200 if (udev_rules)
201- g_string_free_to_file(udev_rules, rootdir, "run/udev/rules.d/90-netplan.rules", NULL);
202+ _netplan_g_string_free_to_file_with_permissions(udev_rules, rootdir, "run/udev/rules.d/90-netplan.rules", NULL, "root", "root", 0640);
203 return TRUE;
204 }
205
206diff --git a/src/openvswitch.c b/src/openvswitch.c
207index 7479267d..d8b2fefb 100644
208--- a/src/openvswitch.c
209+++ b/src/openvswitch.c
210@@ -62,7 +62,7 @@ write_ovs_systemd_unit(const char* id, const GString* cmds, const char* rootdir,
211 g_string_append(s, "\n[Service]\nType=oneshot\n");
212 g_string_append(s, cmds->str);
213
214- g_string_free_to_file(s, rootdir, path, NULL);
215+ _netplan_g_string_free_to_file_with_permissions(s, rootdir, path, NULL, "root", "root", 0640);
216
217 safe_mkdir_p_dir(link);
218 if (symlink(path, link) < 0 && errno != EEXIST) {
219diff --git a/src/sriov.c b/src/sriov.c
220index 966718f4..1b8af8a6 100644
221--- a/src/sriov.c
222+++ b/src/sriov.c
223@@ -48,7 +48,7 @@ write_sriov_rebind_systemd_unit(const GString* pfs, const char* rootdir, GError*
224 g_string_append(s, "\n[Service]\nType=oneshot\n");
225 g_string_append_printf(s, "ExecStart=" SBINDIR "/netplan rebind %s\n", pfs->str);
226
227- g_string_free_to_file(s, rootdir, path, NULL);
228+ _netplan_g_string_free_to_file_with_permissions(s, rootdir, path, NULL, "root", "root", 0640);
229
230 safe_mkdir_p_dir(link);
231 if (symlink(path, link) < 0 && errno != EEXIST) {
232diff --git a/src/util-internal.h b/src/util-internal.h
233index d7875530..4a17219e 100644
234--- a/src/util-internal.h
235+++ b/src/util-internal.h
236@@ -39,6 +39,9 @@ safe_mkdir_p_dir(const char* file_path);
237 NETPLAN_INTERNAL void
238 g_string_free_to_file(GString* s, const char* rootdir, const char* path, const char* suffix);
239
240+void
241+_netplan_g_string_free_to_file_with_permissions(GString* s, const char* rootdir, const char* path, const char* suffix, const char* owner, const char* group, mode_t mode);
242+
243 NETPLAN_INTERNAL void
244 unlink_glob(const char* rootdir, const char* _glob);
245
246diff --git a/src/util.c b/src/util.c
247index 841ec120..e6d8c99b 100644
248--- a/src/util.c
249+++ b/src/util.c
250@@ -19,6 +19,9 @@
251 #include <unistd.h>
252 #include <arpa/inet.h>
253 #include <errno.h>
254+#include <sys/types.h>
255+#include <pwd.h>
256+#include <grp.h>
257
258 #include <glib.h>
259 #include <glib/gprintf.h>
260@@ -81,6 +84,49 @@ void g_string_free_to_file(GString* s, const char* rootdir, const char* path, co
261 }
262 }
263
264+void _netplan_g_string_free_to_file_with_permissions(GString* s, const char* rootdir, const char* path, const char* suffix, const char* owner, const char* group, mode_t mode)
265+{
266+ g_autofree char* full_path = NULL;
267+ g_autofree char* path_suffix = NULL;
268+ g_autofree char* contents = g_string_free(s, FALSE);
269+ GError* error = NULL;
270+ struct passwd* pw = NULL;
271+ struct group* gr = NULL;
272+ int ret = 0;
273+
274+ path_suffix = g_strjoin(NULL, path, suffix, NULL);
275+ full_path = g_build_path(G_DIR_SEPARATOR_S, rootdir ?: G_DIR_SEPARATOR_S, path_suffix, NULL);
276+ safe_mkdir_p_dir(full_path);
277+ if (!g_file_set_contents_full(full_path, contents, -1, G_FILE_SET_CONTENTS_CONSISTENT | G_FILE_SET_CONTENTS_ONLY_EXISTING, mode, &error)) {
278+ /* the mkdir() just succeeded, there is no sensible
279+ * method to test this without root privileges, bind mounts, and
280+ * simulating ENOSPC */
281+ // LCOV_EXCL_START
282+ g_fprintf(stderr, "ERROR: cannot create file %s: %s\n", path, error->message);
283+ exit(1);
284+ // LCOV_EXCL_STOP
285+ }
286+
287+ /* Here we take the owner and group names and look up for their IDs in the passwd and group files.
288+ * It's OK to fail to set the owners and mode as this code will be called from unit tests.
289+ * The autopkgtests will check if the owner/group and mode are correctly set.
290+ */
291+ pw = getpwnam(owner);
292+ if (!pw) {
293+ g_debug("Failed to determine the UID of user %s: %s", owner, strerror(errno)); // LCOV_EXCL_LINE
294+ }
295+ gr = getgrnam(group);
296+ if (!gr) {
297+ g_debug("Failed to determine the GID of group %s: %s", group, strerror(errno)); // LCOV_EXCL_LINE
298+ }
299+ if (pw && gr) {
300+ ret = chown(full_path, pw->pw_uid, gr->gr_gid);
301+ if (ret != 0) {
302+ g_debug("Failed to set owner and group for file %s: %s", full_path, strerror(errno));
303+ }
304+ }
305+}
306+
307 /**
308 * Remove all files matching given glob.
309 */
310diff --git a/tests/generator/test_auth.py b/tests/generator/test_auth.py
311index 3d201092..9bbc0b1c 100644
312--- a/tests/generator/test_auth.py
313+++ b/tests/generator/test_auth.py
314@@ -228,7 +228,7 @@ network={
315
316 with open(os.path.join(self.workdir.name, 'run/systemd/system/netplan-wpa-eth0.service')) as f:
317 self.assertEqual(f.read(), SD_WPA % {'iface': 'eth0', 'drivers': 'wired'})
318- self.assertEqual(stat.S_IMODE(os.fstat(f.fileno()).st_mode), 0o644)
319+ self.assertEqual(stat.S_IMODE(os.fstat(f.fileno()).st_mode), 0o640)
320 self.assertTrue(os.path.islink(os.path.join(
321 self.workdir.name, 'run/systemd/system/systemd-networkd.service.wants/netplan-wpa-eth0.service')))
322
323diff --git a/tests/generator/test_wifis.py b/tests/generator/test_wifis.py
324index 1a4ead23..0130a6eb 100644
325--- a/tests/generator/test_wifis.py
326+++ b/tests/generator/test_wifis.py
327@@ -136,7 +136,7 @@ network={
328 self.workdir.name, 'run/systemd/system/netplan-wpa-wl0.service')))
329 with open(os.path.join(self.workdir.name, 'run/systemd/system/netplan-wpa-wl0.service')) as f:
330 self.assertEqual(f.read(), SD_WPA % {'iface': 'wl0', 'drivers': 'nl80211,wext'})
331- self.assertEqual(stat.S_IMODE(os.fstat(f.fileno()).st_mode), 0o644)
332+ self.assertEqual(stat.S_IMODE(os.fstat(f.fileno()).st_mode), 0o640)
333 self.assertTrue(os.path.islink(os.path.join(
334 self.workdir.name, 'run/systemd/system/systemd-networkd.service.wants/netplan-wpa-wl0.service')))
335
336diff --git a/tests/integration/base.py b/tests/integration/base.py
337index b0863854..0606b1e3 100644
338--- a/tests/integration/base.py
339+++ b/tests/integration/base.py
340@@ -31,6 +31,9 @@ import unittest
341 import shutil
342 import gi
343 import glob
344+import json
345+import pwd
346+import grp
347
348 # make sure we point to libnetplan properly.
349 os.environ.update({'LD_LIBRARY_PATH': '.:{}'.format(os.environ.get('LD_LIBRARY_PATH'))})
350@@ -321,6 +324,89 @@ class IntegrationTestsBase(unittest.TestCase):
351 if state:
352 self.wait_output(['ip', 'addr', 'show', iface], state, 30)
353
354+ # Assert file permissions
355+ self.assert_file_permissions()
356+
357+ def assert_file_permissions(self):
358+ """ Check if the generated files have the expected permissions """
359+
360+ nd_expected_mode = 0o100640
361+ nd_expected_owner = 'root'
362+ nd_expected_group = 'systemd-network'
363+
364+ sd_expected_mode = 0o100640
365+ sd_expected_owner = 'root'
366+ sd_expected_group = 'root'
367+
368+ udev_expected_mode = 0o100640
369+ udev_expected_owner = 'root'
370+ udev_expected_group = 'root'
371+
372+ nm_expected_mode = 0o100600
373+ nm_expected_owner = 'root'
374+ nm_expected_group = 'root'
375+
376+ wpa_expected_mode = 0o100600
377+ wpa_expected_owner = 'root'
378+ wpa_expected_group = 'root'
379+
380+ # Check systemd-networkd files
381+ base_path = '/run/systemd/network'
382+ files = glob.glob(f'{base_path}/*.network') + glob.glob(f'{base_path}/*.netdev')
383+ for file in files:
384+ res = os.stat(file)
385+ user = pwd.getpwuid(res.st_uid)
386+ group = grp.getgrgid(res.st_gid)
387+ self.assertEqual(res.st_mode, nd_expected_mode, f'file {file}')
388+ self.assertEqual(user.pw_name, nd_expected_owner, f'file {file}')
389+ self.assertEqual(group.gr_name, nd_expected_group, f'file {file}')
390+
391+ # Check Network Manager files
392+ base_path = '/run/NetworkManager/system-connections'
393+ files = glob.glob(f'{base_path}/*.nmconnection')
394+ for file in files:
395+ res = os.stat(file)
396+ user = pwd.getpwuid(res.st_uid)
397+ group = grp.getgrgid(res.st_gid)
398+ self.assertEqual(res.st_mode, nm_expected_mode, f'file {file}')
399+ self.assertEqual(user.pw_name, nm_expected_owner, f'file {file}')
400+ self.assertEqual(group.gr_name, nm_expected_group, f'file {file}')
401+
402+ # Check wpa_supplicant configuration files
403+ base_path = '/run/netplan'
404+ files = glob.glob(f'{base_path}/wpa-*.conf')
405+ for file in files:
406+ res = os.stat(file)
407+ user = pwd.getpwuid(res.st_uid)
408+ group = grp.getgrgid(res.st_gid)
409+ self.assertEqual(res.st_mode, wpa_expected_mode, f'file {file}')
410+ self.assertEqual(user.pw_name, wpa_expected_owner, f'file {file}')
411+ self.assertEqual(group.gr_name, wpa_expected_group, f'file {file}')
412+
413+ # Check systemd service unit files
414+ base_path = '/run/systemd/system/'
415+ files = glob.glob(f'{base_path}/netplan-*.service')
416+ files += glob.glob(f'{base_path}/systemd-networkd-wait-online.service.d/*.conf')
417+ for file in files:
418+ res = os.stat(file)
419+ user = pwd.getpwuid(res.st_uid)
420+ group = grp.getgrgid(res.st_gid)
421+ self.assertEqual(res.st_mode, sd_expected_mode, f'file {file}')
422+ self.assertEqual(user.pw_name, sd_expected_owner, f'file {file}')
423+ self.assertEqual(group.gr_name, sd_expected_group, f'file {file}')
424+
425+ # Check systemd-udevd files
426+ udev_path = '/run/udev/rules.d'
427+ link_path = '/run/systemd/network'
428+ files = glob.glob(f'{udev_path}/*-netplan*.rules') + glob.glob(f'{link_path}/*.link')
429+ for file in files:
430+ res = os.stat(file)
431+ user = pwd.getpwuid(res.st_uid)
432+ group = grp.getgrgid(res.st_gid)
433+ self.assertEqual(res.st_mode, udev_expected_mode, f'file {file}')
434+ self.assertEqual(user.pw_name, udev_expected_owner, f'file {file}')
435+ self.assertEqual(group.gr_name, udev_expected_group, f'file {file}')
436+
437 def state(self, iface, state):
438 '''Tell generate_and_settle() to wait for a specific state'''
439 return iface + '/' + state
440--
4412.34.1
442
diff --git a/meta-networking/recipes-connectivity/netplan/netplan_0.104.bb b/meta-networking/recipes-connectivity/netplan/netplan_0.104.bb
index ea944fa9db..c59797c219 100644
--- a/meta-networking/recipes-connectivity/netplan/netplan_0.104.bb
+++ b/meta-networking/recipes-connectivity/netplan/netplan_0.104.bb
@@ -17,6 +17,7 @@ PV = "0.104"
17SRC_URI = "git://github.com/CanonicalLtd/netplan.git;branch=main;protocol=https \ 17SRC_URI = "git://github.com/CanonicalLtd/netplan.git;branch=main;protocol=https \
18 file://0001-Makefile-do-not-use-Werror.patch \ 18 file://0001-Makefile-do-not-use-Werror.patch \
19 file://0001-Makefile-fix-parallel-build-failure.patch \ 19 file://0001-Makefile-fix-parallel-build-failure.patch \
20 file://CVE-2022-4968.patch \
20 " 21 "
21 22
22SRC_URI:append:libc-musl = " file://0001-don-t-fail-if-GLOB_BRACE-is-not-defined.patch" 23SRC_URI:append:libc-musl = " file://0001-don-t-fail-if-GLOB_BRACE-is-not-defined.patch"