Merge ~danilogondolfo/netplan/+git/ubuntu:mantic_0_107_1_sru_with_security_fixes into ~ubuntu-core-dev/netplan/+git/ubuntu:ubuntu-mantic
- Git
- lp:~danilogondolfo/netplan/+git/ubuntu
- mantic_0_107_1_sru_with_security_fixes
- Merge into ubuntu-mantic
Proposed by
Danilo Egea Gondolfo
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | ed684b8a3eb282b9bc7c0f18ad6b2249e7f3ef30 | ||||
Proposed branch: | ~danilogondolfo/netplan/+git/ubuntu:mantic_0_107_1_sru_with_security_fixes | ||||
Merge into: | ~ubuntu-core-dev/netplan/+git/ubuntu:ubuntu-mantic | ||||
Diff against target: |
2060 lines (+2000/-1) 8 files modified
debian/changelog (+20/-1) debian/netplan-generator.postinst (+15/-0) debian/patches/lp2065738/0012-cli-generate-call-daemon-reload-after-generate.patch (+82/-0) debian/patches/lp2065738/0013-libnetplan-use-more-restrictive-file-permissions.patch (+435/-0) debian/patches/lp2066258/0014-libnetplan-escape-control-characters.patch (+863/-0) debian/patches/lp2066258/0015-backends-escape-file-paths.patch (+288/-0) debian/patches/lp2066258/0016-backends-escape-semicolons-in-service-units.patch (+292/-0) debian/patches/series (+5/-0) |
||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Lukas Märdian | Approve | ||
Ubuntu Core Development Team | Pending | ||
Review via email: mp+468523@code.launchpad.net |
Commit message
Description of the change
A PPA with this change can be found at https:/
netplan autopkgtest results
The armhf vrfs tests are failing due to the problem related to the container and the kernel in the host.
network-manager autopkgtest results
To post a comment you must log in.
Revision history for this message
Danilo Egea Gondolfo (danilogondolfo) wrote : | # |
I can't believe I left the ~ppa in the changelog again........
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/debian/changelog b/debian/changelog |
2 | index d28e7c5..ad8fa95 100644 |
3 | --- a/debian/changelog |
4 | +++ b/debian/changelog |
5 | @@ -1,4 +1,4 @@ |
6 | -netplan.io (0.107.1-3ubuntu0.23.10.1) mantic; urgency=medium |
7 | +netplan.io (0.107.1-3ubuntu0.23.10.1~ppa10) mantic; urgency=medium |
8 | |
9 | * Backport netplan.io 0.107.1-3 to 23.10 (LP: #2058051): |
10 | - wifi: add support for WPA3-Enterprise (LP: 2029876) (!402) |
11 | @@ -14,6 +14,25 @@ netplan.io (0.107.1-3ubuntu0.23.10.1) mantic; urgency=medium |
12 | - doc: create libnetplan apidoc structure (!423) |
13 | - inc: Start documenting public API (!423) |
14 | - doc: Update 'Netplan everywhere' for 23.10 (!418) |
15 | + SECURITY UPDATE: weak permissions on secret files, command injection |
16 | + - d/p/lp2065738/0014-libnetplan-use-more-restrictive-file-permissions.patch: |
17 | + Use more restrictive file permissions to prevent unprivileged users to |
18 | + read sensitive data from back end files (LP: 2065738, 1987842) |
19 | + - CVE-2022-4968 |
20 | + - d/p/lp2066258/0015-libnetplan-escape-control-characters.patch: |
21 | + Escape control characters in the parser and double quotes in backend |
22 | + files. |
23 | + - d/p/lp2066258/0016-backends-escape-file-paths.patch: |
24 | + Escape special characters in file paths. |
25 | + - d/p/lp2066258/0017-backends-escape-semicolons-in-service-units.patch: |
26 | + Escape isolated semicolons in systemd service units. (LP: 2066258) |
27 | + - debian/netplan-generator.postinst: Add a postinst maintainer script to |
28 | + call the generator. It's needed so the file permissions fixes will be |
29 | + applied automatically. |
30 | + - d/p/lp2065738/0012-cli-generate-call-daemon-reload-after-generate.patch: |
31 | + Call daemon-reload after generate. This is required due to the NM |
32 | + integration. NM doesn't have the capability required to set file owners |
33 | + and now we set the systemd-network group to networkd related files. |
34 | Bug fixes: |
35 | - test:ovs: Avoid NetworkManager taking contol, breaking a test |
36 | - parse: allow COMMON_LINK_HANDLERS for VRFs (!401) |
37 | diff --git a/debian/netplan-generator.postinst b/debian/netplan-generator.postinst |
38 | new file mode 100644 |
39 | index 0000000..f805c24 |
40 | --- /dev/null |
41 | +++ b/debian/netplan-generator.postinst |
42 | @@ -0,0 +1,15 @@ |
43 | +#!/bin/sh |
44 | + |
45 | +set -e |
46 | + |
47 | +# Calling the generator after installation to mitigate CVE-2022-4968 |
48 | +# We avoid calling the generator if the system doesn't have networkd files to be fixed (LP: #2071333) |
49 | +if [ "$1" = configure ]; then |
50 | + FILES=$(find /run/systemd/network/ -type f -regex ".*-netplan.*\.\(network\|netdev\)" 2>/dev/null || true) |
51 | + if [ -n "${FILES}" ]; then |
52 | + /usr/libexec/netplan/generate 2>/dev/null || echo "WARNING: Netplan could not re-generate network configuration. Please run 'netplan generate' to see details." |
53 | + fi |
54 | +fi |
55 | + |
56 | +#DEBHELPER# |
57 | + |
58 | diff --git a/debian/patches/lp2065738/0012-cli-generate-call-daemon-reload-after-generate.patch b/debian/patches/lp2065738/0012-cli-generate-call-daemon-reload-after-generate.patch |
59 | new file mode 100644 |
60 | index 0000000..acafe2b |
61 | --- /dev/null |
62 | +++ b/debian/patches/lp2065738/0012-cli-generate-call-daemon-reload-after-generate.patch |
63 | @@ -0,0 +1,82 @@ |
64 | +From: =?utf-8?q?Lukas_M=C3=A4rdian?= <slyon@ubuntu.com> |
65 | +Date: Wed, 17 Apr 2024 10:23:16 +0200 |
66 | +Subject: cli/generate: call daemon-reload after generate |
67 | + |
68 | +This is required due to the Network Manager integration. NM doesn't have |
69 | +the capability required to change the configuration files owners. |
70 | +Because of that, networkd files that are regenerated when NM is called |
71 | +will have root:root as owner and group. As we changed the default mode |
72 | +to 0640, networkd wouldn't be able to read the files and the |
73 | +configuration would be ignored. |
74 | + |
75 | +Origin: https://github.com/canonical/netplan/commit/1892487a44f06d3af9620a0ca54ed6e32d1ae963 |
76 | +Origin: https://github.com/canonical/netplan/commit/223e3d3dd10c44149e2c9b9741900d11ebec6f97 |
77 | +Origin: https://github.com/canonical/netplan/commit/ea60510cf741afe73b59470af9f812a36140dae0 |
78 | + |
79 | +--- |
80 | + netplan_cli/cli/commands/generate.py | 11 ++++++++++- |
81 | + netplan_cli/cli/utils.py | 2 +- |
82 | + tests/cli_legacy.py | 3 +-- |
83 | + tests/test_utils.py | 2 +- |
84 | + 4 files changed, 13 insertions(+), 5 deletions(-) |
85 | + |
86 | +diff --git a/netplan_cli/cli/commands/generate.py b/netplan_cli/cli/commands/generate.py |
87 | +index f84b171..ba51eb0 100644 |
88 | +--- a/netplan_cli/cli/commands/generate.py |
89 | ++++ b/netplan_cli/cli/commands/generate.py |
90 | +@@ -81,5 +81,14 @@ class NetplanGenerate(utils.NetplanCommand): |
91 | + if self.mapping: |
92 | + argv += ['--mapping', self.mapping] |
93 | + logging.debug('command generate: running %s', argv) |
94 | ++ res = subprocess.call(argv) |
95 | ++ # reload systemd, as we might have changed service units, such as |
96 | ++ # /run/systemd/system/systemd-networkd-wait-online.service.d/10-netplan.conf |
97 | ++ # Skip it if --mapping is used as nothing will be generated |
98 | ++ if self.mapping is None: |
99 | ++ try: |
100 | ++ utils.systemctl_daemon_reload() |
101 | ++ except subprocess.CalledProcessError as e: |
102 | ++ logging.warning(e) |
103 | + # FIXME: os.execv(argv[0], argv) would be better but fails coverage |
104 | +- sys.exit(subprocess.call(argv)) |
105 | ++ sys.exit(res) |
106 | +diff --git a/netplan_cli/cli/utils.py b/netplan_cli/cli/utils.py |
107 | +index f913630..237e006 100644 |
108 | +--- a/netplan_cli/cli/utils.py |
109 | ++++ b/netplan_cli/cli/utils.py |
110 | +@@ -160,7 +160,7 @@ def systemctl_is_installed(unit_pattern): |
111 | + |
112 | + def systemctl_daemon_reload(): |
113 | + '''Reload systemd unit files from disk and re-calculate its dependencies''' |
114 | +- subprocess.check_call(['systemctl', 'daemon-reload']) |
115 | ++ subprocess.check_call(['systemctl', 'daemon-reload', '--no-ask-password']) |
116 | + |
117 | + |
118 | + def ip_addr_flush(iface): |
119 | +diff --git a/tests/cli_legacy.py b/tests/cli_legacy.py |
120 | +index 897043e..decde81 100755 |
121 | +--- a/tests/cli_legacy.py |
122 | ++++ b/tests/cli_legacy.py |
123 | +@@ -98,8 +98,7 @@ class TestGenerate(unittest.TestCase): |
124 | + enlol: {dhcp4: yes}''') |
125 | + os.chmod(path_a, mode=0o600) |
126 | + os.chmod(path_b, mode=0o600) |
127 | +- out = subprocess.check_output(exe_cli + ['generate', '--root-dir', self.workdir.name], stderr=subprocess.STDOUT) |
128 | +- self.assertEqual(out, b'') |
129 | ++ subprocess.check_call(exe_cli + ['generate', '--root-dir', self.workdir.name]) |
130 | + self.assertEqual(os.listdir(os.path.join(self.workdir.name, 'run', 'systemd', 'network')), |
131 | + ['10-netplan-enlol.network']) |
132 | + |
133 | +diff --git a/tests/test_utils.py b/tests/test_utils.py |
134 | +index eefd334..37cf262 100644 |
135 | +--- a/tests/test_utils.py |
136 | ++++ b/tests/test_utils.py |
137 | +@@ -380,7 +380,7 @@ class TestUtils(unittest.TestCase): |
138 | + os.environ['PATH'] = os.path.dirname(self.mock_cmd.path) + os.pathsep + path_env |
139 | + utils.systemctl_daemon_reload() |
140 | + self.assertEqual(self.mock_cmd.calls(), [ |
141 | +- ['systemctl', 'daemon-reload'] |
142 | ++ ['systemctl', 'daemon-reload', '--no-ask-password'] |
143 | + ]) |
144 | + |
145 | + def test_ip_addr_flush(self): |
146 | diff --git a/debian/patches/lp2065738/0013-libnetplan-use-more-restrictive-file-permissions.patch b/debian/patches/lp2065738/0013-libnetplan-use-more-restrictive-file-permissions.patch |
147 | new file mode 100644 |
148 | index 0000000..9ebce53 |
149 | --- /dev/null |
150 | +++ b/debian/patches/lp2065738/0013-libnetplan-use-more-restrictive-file-permissions.patch |
151 | @@ -0,0 +1,435 @@ |
152 | +From: Danilo Egea Gondolfo <danilogondolfo@gmail.com> |
153 | +Date: Wed, 22 May 2024 15:44:16 +0100 |
154 | +Subject: libnetplan: use more restrictive file permissions |
155 | + |
156 | +A new util.c:_netplan_g_string_free_to_file_with_permissions() was added |
157 | +and accepts the owner, group and file mode as arguments. When these |
158 | +properties can't be set, when the generator is called by a non-root user |
159 | +for example, it will not hard-fail. This function is called by unit |
160 | +tests where we can't set the owner to a privileged account for example. |
161 | + |
162 | +When generating backend files, use more restrictive permissions: |
163 | + |
164 | +networkd related files will be owned by root:systemd-network and have |
165 | +mode 0640. |
166 | + |
167 | +service unit files will be owned by root:root and have mode 0640. |
168 | +udevd files will be owned by root:root with mode 0640. |
169 | + |
170 | +wpa_supplicant and Network Manager files will continue with the existing |
171 | +permissions. |
172 | + |
173 | +Autopkgtests will check if the permissions are set as expected when |
174 | +calling the generator. |
175 | + |
176 | +This fix addresses CVE-2022-4968 |
177 | +--- |
178 | + src/networkd.c | 36 ++++-------------- |
179 | + src/networkd.h | 2 + |
180 | + src/nm.c | 4 +- |
181 | + src/openvswitch.c | 2 +- |
182 | + src/sriov.c | 2 +- |
183 | + src/util-internal.h | 3 ++ |
184 | + src/util.c | 46 +++++++++++++++++++++++ |
185 | + tests/generator/test_auth.py | 2 +- |
186 | + tests/generator/test_wifis.py | 2 +- |
187 | + tests/integration/base.py | 85 +++++++++++++++++++++++++++++++++++++++++++ |
188 | + 10 files changed, 149 insertions(+), 35 deletions(-) |
189 | + |
190 | +diff --git a/src/networkd.c b/src/networkd.c |
191 | +index 5554c99..1677381 100644 |
192 | +--- a/src/networkd.c |
193 | ++++ b/src/networkd.c |
194 | +@@ -222,7 +222,6 @@ static void |
195 | + write_link_file(const NetplanNetDefinition* def, const char* rootdir, const char* path) |
196 | + { |
197 | + GString* s = NULL; |
198 | +- mode_t orig_umask; |
199 | + |
200 | + /* Don't write .link files for virtual devices; they use .netdev instead. |
201 | + * Don't write .link files for MODEM devices, as they aren't supported by networkd. |
202 | +@@ -284,9 +283,7 @@ write_link_file(const NetplanNetDefinition* def, const char* rootdir, const char |
203 | + g_string_append_printf(s, "LargeReceiveOffload=%s\n", |
204 | + (def->large_receive_offload ? "true" : "false")); |
205 | + |
206 | +- orig_umask = umask(022); |
207 | +- g_string_free_to_file(s, rootdir, path, ".link"); |
208 | +- umask(orig_umask); |
209 | ++ _netplan_g_string_free_to_file_with_permissions(s, rootdir, path, ".link", "root", "root", 0640); |
210 | + } |
211 | + |
212 | + static gboolean |
213 | +@@ -304,7 +301,7 @@ write_regdom(const NetplanNetDefinition* def, const char* rootdir, GError** erro |
214 | + g_string_append(s, "\n[Service]\nType=oneshot\n"); |
215 | + g_string_append_printf(s, "ExecStart="SBINDIR"/iw reg set %s\n", def->regulatory_domain); |
216 | + |
217 | +- g_string_free_to_file(s, rootdir, path, NULL); |
218 | ++ _netplan_g_string_free_to_file_with_permissions(s, rootdir, path, NULL, "root", "root", 0640); |
219 | + safe_mkdir_p_dir(link); |
220 | + if (symlink(path, link) < 0 && errno != EEXIST) { |
221 | + // LCOV_EXCL_START |
222 | +@@ -484,7 +481,6 @@ static void |
223 | + write_netdev_file(const NetplanNetDefinition* def, const char* rootdir, const char* path) |
224 | + { |
225 | + GString* s = NULL; |
226 | +- mode_t orig_umask; |
227 | + |
228 | + g_assert(def->type >= NETPLAN_DEF_TYPE_VIRTUAL); |
229 | + |
230 | +@@ -580,11 +576,7 @@ write_netdev_file(const NetplanNetDefinition* def, const char* rootdir, const ch |
231 | + default: g_assert_not_reached(); // LCOV_EXCL_LINE |
232 | + } |
233 | + |
234 | +- /* these do not contain secrets and need to be readable by |
235 | +- * systemd-networkd - LP: #1736965 */ |
236 | +- orig_umask = umask(022); |
237 | +- g_string_free_to_file(s, rootdir, path, ".netdev"); |
238 | +- umask(orig_umask); |
239 | ++ _netplan_g_string_free_to_file_with_permissions(s, rootdir, path, ".netdev", "root", NETWORKD_GROUP, 0640); |
240 | + } |
241 | + |
242 | + static void |
243 | +@@ -728,7 +720,6 @@ netplan_netdef_write_network_file( |
244 | + g_autoptr(GString) network = NULL; |
245 | + g_autoptr(GString) link = NULL; |
246 | + GString* s = NULL; |
247 | +- mode_t orig_umask; |
248 | + gboolean is_optional = def->optional; |
249 | + |
250 | + SET_OPT_OUT_PTR(has_been_written, FALSE); |
251 | +@@ -979,11 +970,7 @@ netplan_netdef_write_network_file( |
252 | + if (network->len > 0) |
253 | + g_string_append_printf(s, "\n[Network]\n%s", network->str); |
254 | + |
255 | +- /* these do not contain secrets and need to be readable by |
256 | +- * systemd-networkd - LP: #1736965 */ |
257 | +- orig_umask = umask(022); |
258 | +- g_string_free_to_file(s, rootdir, path, ".network"); |
259 | +- umask(orig_umask); |
260 | ++ _netplan_g_string_free_to_file_with_permissions(s, rootdir, path, ".network", "root", NETWORKD_GROUP, 0640); |
261 | + } |
262 | + |
263 | + SET_OPT_OUT_PTR(has_been_written, TRUE); |
264 | +@@ -995,7 +982,6 @@ write_rules_file(const NetplanNetDefinition* def, const char* rootdir) |
265 | + { |
266 | + GString* s = NULL; |
267 | + g_autofree char* path = g_strjoin(NULL, "run/udev/rules.d/99-netplan-", def->id, ".rules", NULL); |
268 | +- mode_t orig_umask; |
269 | + |
270 | + /* do we need to write a .rules file? |
271 | + * It's only required for reliably setting the name of a physical device |
272 | +@@ -1029,9 +1015,7 @@ write_rules_file(const NetplanNetDefinition* def, const char* rootdir) |
273 | + |
274 | + g_string_append_printf(s, "NAME=\"%s\"\n", def->set_name); |
275 | + |
276 | +- orig_umask = umask(022); |
277 | +- g_string_free_to_file(s, rootdir, path, NULL); |
278 | +- umask(orig_umask); |
279 | ++ _netplan_g_string_free_to_file_with_permissions(s, rootdir, path, NULL, "root", "root", 0640); |
280 | + } |
281 | + |
282 | + static gboolean |
283 | +@@ -1180,7 +1164,6 @@ static void |
284 | + write_wpa_unit(const NetplanNetDefinition* def, const char* rootdir) |
285 | + { |
286 | + g_autofree gchar *stdouth = NULL; |
287 | +- mode_t orig_umask; |
288 | + |
289 | + stdouth = systemd_escape(def->id); |
290 | + |
291 | +@@ -1199,9 +1182,7 @@ write_wpa_unit(const NetplanNetDefinition* def, const char* rootdir) |
292 | + } else { |
293 | + g_string_append(s, " -Dnl80211,wext\n"); |
294 | + } |
295 | +- orig_umask = umask(022); |
296 | +- g_string_free_to_file(s, rootdir, path, NULL); |
297 | +- umask(orig_umask); |
298 | ++ _netplan_g_string_free_to_file_with_permissions(s, rootdir, path, NULL, "root", "root", 0640); |
299 | + } |
300 | + |
301 | + static gboolean |
302 | +@@ -1210,7 +1191,6 @@ write_wpa_conf(const NetplanNetDefinition* def, const char* rootdir, GError** er |
303 | + GHashTableIter iter; |
304 | + GString* s = g_string_new("ctrl_interface=/run/wpa_supplicant\n\n"); |
305 | + g_autofree char* path = g_strjoin(NULL, "run/netplan/wpa-", def->id, ".conf", NULL); |
306 | +- mode_t orig_umask; |
307 | + |
308 | + g_debug("%s: Creating wpa_supplicant configuration file %s", def->id, path); |
309 | + if (def->type == NETPLAN_DEF_TYPE_WIFI) { |
310 | +@@ -1299,9 +1279,7 @@ write_wpa_conf(const NetplanNetDefinition* def, const char* rootdir, GError** er |
311 | + } |
312 | + |
313 | + /* use tight permissions as this contains secrets */ |
314 | +- orig_umask = umask(077); |
315 | +- g_string_free_to_file(s, rootdir, path, NULL); |
316 | +- umask(orig_umask); |
317 | ++ _netplan_g_string_free_to_file_with_permissions(s, rootdir, path, NULL, "root", "root", 0600); |
318 | + return TRUE; |
319 | + } |
320 | + |
321 | +diff --git a/src/networkd.h b/src/networkd.h |
322 | +index a7092b2..0214e43 100644 |
323 | +--- a/src/networkd.h |
324 | ++++ b/src/networkd.h |
325 | +@@ -20,6 +20,8 @@ |
326 | + #include "netplan.h" |
327 | + #include <glib.h> |
328 | + |
329 | ++#define NETWORKD_GROUP "systemd-network" |
330 | ++ |
331 | + NETPLAN_INTERNAL gboolean |
332 | + netplan_netdef_write_networkd( |
333 | + const NetplanState* np_state, |
334 | +diff --git a/src/nm.c b/src/nm.c |
335 | +index 4d6f1fe..d5dad98 100644 |
336 | +--- a/src/nm.c |
337 | ++++ b/src/nm.c |
338 | +@@ -1149,13 +1149,13 @@ netplan_state_finish_nm_write( |
339 | + |
340 | + /* write generated NetworkManager drop-in config */ |
341 | + if (nm_conf->len > 0) |
342 | +- g_string_free_to_file(nm_conf, rootdir, "run/NetworkManager/conf.d/netplan.conf", NULL); |
343 | ++ _netplan_g_string_free_to_file_with_permissions(nm_conf, rootdir, "run/NetworkManager/conf.d/netplan.conf", NULL, "root", "root", 0640); |
344 | + else |
345 | + g_string_free(nm_conf, TRUE); |
346 | + |
347 | + /* write generated udev rules */ |
348 | + if (udev_rules->len > 0) |
349 | +- g_string_free_to_file(udev_rules, rootdir, "run/udev/rules.d/90-netplan.rules", NULL); |
350 | ++ _netplan_g_string_free_to_file_with_permissions(udev_rules, rootdir, "run/udev/rules.d/90-netplan.rules", NULL, "root", "root", 0640); |
351 | + else |
352 | + g_string_free(udev_rules, TRUE); |
353 | + |
354 | +diff --git a/src/openvswitch.c b/src/openvswitch.c |
355 | +index d4af861..276762e 100644 |
356 | +--- a/src/openvswitch.c |
357 | ++++ b/src/openvswitch.c |
358 | +@@ -62,7 +62,7 @@ write_ovs_systemd_unit(const char* id, const GString* cmds, const char* rootdir, |
359 | + g_string_append(s, "\n[Service]\nType=oneshot\nTimeoutStartSec=10s\n"); |
360 | + g_string_append(s, cmds->str); |
361 | + |
362 | +- g_string_free_to_file(s, rootdir, path, NULL); |
363 | ++ _netplan_g_string_free_to_file_with_permissions(s, rootdir, path, NULL, "root", "root", 0640); |
364 | + |
365 | + safe_mkdir_p_dir(link); |
366 | + if (symlink(path, link) < 0 && errno != EEXIST) { |
367 | +diff --git a/src/sriov.c b/src/sriov.c |
368 | +index f8117f7..c3cd80d 100644 |
369 | +--- a/src/sriov.c |
370 | ++++ b/src/sriov.c |
371 | +@@ -53,7 +53,7 @@ write_sriov_rebind_systemd_unit(GHashTable* pfs, const char* rootdir, GError** e |
372 | + g_string_truncate(interfaces, interfaces->len-1); /* cut trailing whitespace */ |
373 | + g_string_append_printf(s, "ExecStart=" SBINDIR "/netplan rebind %s\n", interfaces->str); |
374 | + |
375 | +- g_string_free_to_file(s, rootdir, path, NULL); |
376 | ++ _netplan_g_string_free_to_file_with_permissions(s, rootdir, path, NULL, "root", "root", 0640); |
377 | + g_string_free(interfaces, TRUE); |
378 | + |
379 | + safe_mkdir_p_dir(link); |
380 | +diff --git a/src/util-internal.h b/src/util-internal.h |
381 | +index fe41c77..b30745b 100644 |
382 | +--- a/src/util-internal.h |
383 | ++++ b/src/util-internal.h |
384 | +@@ -40,6 +40,9 @@ safe_mkdir_p_dir(const char* file_path); |
385 | + NETPLAN_INTERNAL void |
386 | + g_string_free_to_file(GString* s, const char* rootdir, const char* path, const char* suffix); |
387 | + |
388 | ++void |
389 | ++_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); |
390 | ++ |
391 | + NETPLAN_INTERNAL void |
392 | + unlink_glob(const char* rootdir, const char* _glob); |
393 | + |
394 | +diff --git a/src/util.c b/src/util.c |
395 | +index cbd5ac2..edecdab 100644 |
396 | +--- a/src/util.c |
397 | ++++ b/src/util.c |
398 | +@@ -22,6 +22,9 @@ |
399 | + #include <errno.h> |
400 | + #include <string.h> |
401 | + #include <sys/mman.h> |
402 | ++#include <sys/types.h> |
403 | ++#include <pwd.h> |
404 | ++#include <grp.h> |
405 | + |
406 | + #include <glib.h> |
407 | + #include <glib/gprintf.h> |
408 | +@@ -87,6 +90,49 @@ void g_string_free_to_file(GString* s, const char* rootdir, const char* path, co |
409 | + } |
410 | + } |
411 | + |
412 | ++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) |
413 | ++{ |
414 | ++ g_autofree char* full_path = NULL; |
415 | ++ g_autofree char* path_suffix = NULL; |
416 | ++ g_autofree char* contents = g_string_free(s, FALSE); |
417 | ++ GError* error = NULL; |
418 | ++ struct passwd* pw = NULL; |
419 | ++ struct group* gr = NULL; |
420 | ++ int ret = 0; |
421 | ++ |
422 | ++ path_suffix = g_strjoin(NULL, path, suffix, NULL); |
423 | ++ full_path = g_build_path(G_DIR_SEPARATOR_S, rootdir ?: G_DIR_SEPARATOR_S, path_suffix, NULL); |
424 | ++ safe_mkdir_p_dir(full_path); |
425 | ++ if (!g_file_set_contents_full(full_path, contents, -1, G_FILE_SET_CONTENTS_CONSISTENT | G_FILE_SET_CONTENTS_ONLY_EXISTING, mode, &error)) { |
426 | ++ /* the mkdir() just succeeded, there is no sensible |
427 | ++ * method to test this without root privileges, bind mounts, and |
428 | ++ * simulating ENOSPC */ |
429 | ++ // LCOV_EXCL_START |
430 | ++ g_fprintf(stderr, "ERROR: cannot create file %s: %s\n", path, error->message); |
431 | ++ exit(1); |
432 | ++ // LCOV_EXCL_STOP |
433 | ++ } |
434 | ++ |
435 | ++ /* Here we take the owner and group names and look up for their IDs in the passwd and group files. |
436 | ++ * It's OK to fail to set the owners and mode as this code will be called from unit tests. |
437 | ++ * The autopkgtests will check if the owner/group and mode are correctly set. |
438 | ++ */ |
439 | ++ pw = getpwnam(owner); |
440 | ++ if (!pw) { |
441 | ++ g_debug("Failed to determine the UID of user %s: %s", owner, strerror(errno)); // LCOV_EXCL_LINE |
442 | ++ } |
443 | ++ gr = getgrnam(group); |
444 | ++ if (!gr) { |
445 | ++ g_debug("Failed to determine the GID of group %s: %s", group, strerror(errno)); // LCOV_EXCL_LINE |
446 | ++ } |
447 | ++ if (pw && gr) { |
448 | ++ ret = chown(full_path, pw->pw_uid, gr->gr_gid); |
449 | ++ if (ret != 0) { |
450 | ++ g_debug("Failed to set owner and group for file %s: %s", full_path, strerror(errno)); |
451 | ++ } |
452 | ++ } |
453 | ++} |
454 | ++ |
455 | + /** |
456 | + * Remove all files matching given glob. |
457 | + */ |
458 | +diff --git a/tests/generator/test_auth.py b/tests/generator/test_auth.py |
459 | +index de23adb..d3d886c 100644 |
460 | +--- a/tests/generator/test_auth.py |
461 | ++++ b/tests/generator/test_auth.py |
462 | +@@ -226,7 +226,7 @@ network={ |
463 | + |
464 | + with open(os.path.join(self.workdir.name, 'run/systemd/system/netplan-wpa-eth0.service')) as f: |
465 | + self.assertEqual(f.read(), SD_WPA % {'iface': 'eth0', 'drivers': 'wired'}) |
466 | +- self.assertEqual(stat.S_IMODE(os.fstat(f.fileno()).st_mode), 0o644) |
467 | ++ self.assertEqual(stat.S_IMODE(os.fstat(f.fileno()).st_mode), 0o640) |
468 | + self.assertTrue(os.path.islink(os.path.join( |
469 | + self.workdir.name, 'run/systemd/system/systemd-networkd.service.wants/netplan-wpa-eth0.service'))) |
470 | + |
471 | +diff --git a/tests/generator/test_wifis.py b/tests/generator/test_wifis.py |
472 | +index b875172..610782a 100644 |
473 | +--- a/tests/generator/test_wifis.py |
474 | ++++ b/tests/generator/test_wifis.py |
475 | +@@ -140,7 +140,7 @@ network={ |
476 | + self.workdir.name, 'run/systemd/system/netplan-wpa-wl0.service'))) |
477 | + with open(os.path.join(self.workdir.name, 'run/systemd/system/netplan-wpa-wl0.service')) as f: |
478 | + self.assertEqual(f.read(), SD_WPA % {'iface': 'wl0', 'drivers': 'nl80211,wext'}) |
479 | +- self.assertEqual(stat.S_IMODE(os.fstat(f.fileno()).st_mode), 0o644) |
480 | ++ self.assertEqual(stat.S_IMODE(os.fstat(f.fileno()).st_mode), 0o640) |
481 | + self.assertTrue(os.path.islink(os.path.join( |
482 | + self.workdir.name, 'run/systemd/system/systemd-networkd.service.wants/netplan-wpa-wl0.service'))) |
483 | + |
484 | +diff --git a/tests/integration/base.py b/tests/integration/base.py |
485 | +index 81e8420..948b1c5 100644 |
486 | +--- a/tests/integration/base.py |
487 | ++++ b/tests/integration/base.py |
488 | +@@ -32,6 +32,8 @@ import shutil |
489 | + import gi |
490 | + import glob |
491 | + import json |
492 | ++import pwd |
493 | ++import grp |
494 | + |
495 | + # make sure we point to libnetplan properly. |
496 | + os.environ.update({'LD_LIBRARY_PATH': '.:{}'.format(os.environ.get('LD_LIBRARY_PATH'))}) |
497 | +@@ -367,6 +369,89 @@ class IntegrationTestsBase(unittest.TestCase): |
498 | + if state: |
499 | + self.wait_output(['ip', 'addr', 'show', iface], state, 30) |
500 | + |
501 | ++ # Assert file permissions |
502 | ++ self.assert_file_permissions() |
503 | ++ |
504 | ++ def assert_file_permissions(self): |
505 | ++ """ Check if the generated files have the expected permissions """ |
506 | ++ |
507 | ++ nd_expected_mode = 0o100640 |
508 | ++ nd_expected_owner = 'root' |
509 | ++ nd_expected_group = 'systemd-network' |
510 | ++ |
511 | ++ sd_expected_mode = 0o100640 |
512 | ++ sd_expected_owner = 'root' |
513 | ++ sd_expected_group = 'root' |
514 | ++ |
515 | ++ udev_expected_mode = 0o100640 |
516 | ++ udev_expected_owner = 'root' |
517 | ++ udev_expected_group = 'root' |
518 | ++ |
519 | ++ nm_expected_mode = 0o100600 |
520 | ++ nm_expected_owner = 'root' |
521 | ++ nm_expected_group = 'root' |
522 | ++ |
523 | ++ wpa_expected_mode = 0o100600 |
524 | ++ wpa_expected_owner = 'root' |
525 | ++ wpa_expected_group = 'root' |
526 | ++ |
527 | ++ # Check systemd-networkd files |
528 | ++ base_path = '/run/systemd/network' |
529 | ++ files = glob.glob(f'{base_path}/*.network') + glob.glob(f'{base_path}/*.netdev') |
530 | ++ for file in files: |
531 | ++ res = os.stat(file) |
532 | ++ user = pwd.getpwuid(res.st_uid) |
533 | ++ group = grp.getgrgid(res.st_gid) |
534 | ++ self.assertEqual(res.st_mode, nd_expected_mode, f'file {file}') |
535 | ++ self.assertEqual(user.pw_name, nd_expected_owner, f'file {file}') |
536 | ++ self.assertEqual(group.gr_name, nd_expected_group, f'file {file}') |
537 | ++ |
538 | ++ # Check Network Manager files |
539 | ++ base_path = '/run/NetworkManager/system-connections' |
540 | ++ files = glob.glob(f'{base_path}/*.nmconnection') |
541 | ++ for file in files: |
542 | ++ res = os.stat(file) |
543 | ++ user = pwd.getpwuid(res.st_uid) |
544 | ++ group = grp.getgrgid(res.st_gid) |
545 | ++ self.assertEqual(res.st_mode, nm_expected_mode, f'file {file}') |
546 | ++ self.assertEqual(user.pw_name, nm_expected_owner, f'file {file}') |
547 | ++ self.assertEqual(group.gr_name, nm_expected_group, f'file {file}') |
548 | ++ |
549 | ++ # Check wpa_supplicant configuration files |
550 | ++ base_path = '/run/netplan' |
551 | ++ files = glob.glob(f'{base_path}/wpa-*.conf') |
552 | ++ for file in files: |
553 | ++ res = os.stat(file) |
554 | ++ user = pwd.getpwuid(res.st_uid) |
555 | ++ group = grp.getgrgid(res.st_gid) |
556 | ++ self.assertEqual(res.st_mode, wpa_expected_mode, f'file {file}') |
557 | ++ self.assertEqual(user.pw_name, wpa_expected_owner, f'file {file}') |
558 | ++ self.assertEqual(group.gr_name, wpa_expected_group, f'file {file}') |
559 | ++ |
560 | ++ # Check systemd service unit files |
561 | ++ base_path = '/run/systemd/system/' |
562 | ++ files = glob.glob(f'{base_path}/netplan-*.service') |
563 | ++ files += glob.glob(f'{base_path}/systemd-networkd-wait-online.service.d/*.conf') |
564 | ++ for file in files: |
565 | ++ res = os.stat(file) |
566 | ++ user = pwd.getpwuid(res.st_uid) |
567 | ++ group = grp.getgrgid(res.st_gid) |
568 | ++ self.assertEqual(res.st_mode, sd_expected_mode, f'file {file}') |
569 | ++ self.assertEqual(user.pw_name, sd_expected_owner, f'file {file}') |
570 | ++ self.assertEqual(group.gr_name, sd_expected_group, f'file {file}') |
571 | ++ |
572 | ++ # Check systemd-udevd files |
573 | ++ udev_path = '/run/udev/rules.d' |
574 | ++ link_path = '/run/systemd/network' |
575 | ++ files = glob.glob(f'{udev_path}/*-netplan*.rules') + glob.glob(f'{link_path}/*.link') |
576 | ++ for file in files: |
577 | ++ res = os.stat(file) |
578 | ++ user = pwd.getpwuid(res.st_uid) |
579 | ++ group = grp.getgrgid(res.st_gid) |
580 | ++ self.assertEqual(res.st_mode, udev_expected_mode, f'file {file}') |
581 | ++ self.assertEqual(user.pw_name, udev_expected_owner, f'file {file}') |
582 | ++ self.assertEqual(group.gr_name, udev_expected_group, f'file {file}') |
583 | ++ |
584 | + def state(self, iface, state): |
585 | + '''Tell generate_and_settle() to wait for a specific state''' |
586 | + return iface + '/' + state |
587 | diff --git a/debian/patches/lp2066258/0014-libnetplan-escape-control-characters.patch b/debian/patches/lp2066258/0014-libnetplan-escape-control-characters.patch |
588 | new file mode 100644 |
589 | index 0000000..5c7cad8 |
590 | --- /dev/null |
591 | +++ b/debian/patches/lp2066258/0014-libnetplan-escape-control-characters.patch |
592 | @@ -0,0 +1,863 @@ |
593 | +From: Danilo Egea Gondolfo <danilogondolfo@gmail.com> |
594 | +Date: Wed, 29 May 2024 14:50:55 +0100 |
595 | +Subject: libnetplan: escape control characters |
596 | + |
597 | +Control characters are escaped in the parser using glib's g_strescape. |
598 | +Quotes and backslashes were added to the list of exception. |
599 | + |
600 | +In places where double quotes are not escaped, such as netdef IDs as it |
601 | +is allowed as interface names, they are escaped as needed when |
602 | +generating back end configuration. |
603 | + |
604 | +To support escaping in wpa_supplicant configuration, the syntax for |
605 | +setting the SSID was changed to 'ssid=P"string here"'. With that, |
606 | +escaping is support in a printf-style. |
607 | +--- |
608 | + src/networkd.c | 32 ++++++++++----- |
609 | + src/nm.c | 21 ++++++---- |
610 | + src/parse.c | 92 +++++++++++++++++++++++++++--------------- |
611 | + src/util-internal.h | 3 ++ |
612 | + src/util.c | 11 +++++ |
613 | + tests/generator/test_auth.py | 20 ++++----- |
614 | + tests/generator/test_common.py | 42 +++++++++++++++++-- |
615 | + tests/generator/test_wifis.py | 78 ++++++++++++++++++++++++++--------- |
616 | + 8 files changed, 216 insertions(+), 83 deletions(-) |
617 | + |
618 | +diff --git a/src/networkd.c b/src/networkd.c |
619 | +index 1677381..ac54112 100644 |
620 | +--- a/src/networkd.c |
621 | ++++ b/src/networkd.c |
622 | +@@ -1005,7 +1005,8 @@ write_rules_file(const NetplanNetDefinition* def, const char* rootdir) |
623 | + g_string_append(s, "SUBSYSTEM==\"net\", ACTION==\"add\", "); |
624 | + |
625 | + if (def->match.driver) { |
626 | +- g_string_append_printf(s,"DRIVERS==\"%s\", ", def->match.driver); |
627 | ++ g_autofree char* driver = _netplan_scrub_string(def->match.driver); |
628 | ++ g_string_append_printf(s,"DRIVERS==\"%s\", ", driver); |
629 | + } else { |
630 | + g_string_append(s, "DRIVERS==\"?*\", "); |
631 | + } |
632 | +@@ -1013,7 +1014,8 @@ write_rules_file(const NetplanNetDefinition* def, const char* rootdir) |
633 | + if (def->match.mac) |
634 | + g_string_append_printf(s, "ATTR{address}==\"%s\", ", def->match.mac); |
635 | + |
636 | +- g_string_append_printf(s, "NAME=\"%s\"\n", def->set_name); |
637 | ++ g_autofree char* set_name = _netplan_scrub_string(def->set_name); |
638 | ++ g_string_append_printf(s, "NAME=\"%s\"\n", set_name); |
639 | + |
640 | + _netplan_g_string_free_to_file_with_permissions(s, rootdir, path, NULL, "root", "root", 0640); |
641 | + } |
642 | +@@ -1101,10 +1103,12 @@ append_wpa_auth_conf(GString* s, const NetplanAuthenticationSettings* auth, cons |
643 | + } |
644 | + |
645 | + if (auth->identity) { |
646 | +- g_string_append_printf(s, " identity=\"%s\"\n", auth->identity); |
647 | ++ g_autofree char* identity = _netplan_scrub_string(auth->identity); |
648 | ++ g_string_append_printf(s, " identity=\"%s\"\n", identity); |
649 | + } |
650 | + if (auth->anonymous_identity) { |
651 | +- g_string_append_printf(s, " anonymous_identity=\"%s\"\n", auth->anonymous_identity); |
652 | ++ g_autofree char* anonymous_identity = _netplan_scrub_string(auth->anonymous_identity); |
653 | ++ g_string_append_printf(s, " anonymous_identity=\"%s\"\n", anonymous_identity); |
654 | + } |
655 | + |
656 | + char* psk = NULL; |
657 | +@@ -1142,19 +1146,23 @@ append_wpa_auth_conf(GString* s, const NetplanAuthenticationSettings* auth, cons |
658 | + } |
659 | + } |
660 | + if (auth->ca_certificate) { |
661 | +- g_string_append_printf(s, " ca_cert=\"%s\"\n", auth->ca_certificate); |
662 | ++ g_autofree char* ca_certificate = _netplan_scrub_string(auth->ca_certificate); |
663 | ++ g_string_append_printf(s, " ca_cert=\"%s\"\n", ca_certificate); |
664 | + } |
665 | + if (auth->client_certificate) { |
666 | +- g_string_append_printf(s, " client_cert=\"%s\"\n", auth->client_certificate); |
667 | ++ g_autofree char* client_certificate = _netplan_scrub_string(auth->client_certificate); |
668 | ++ g_string_append_printf(s, " client_cert=\"%s\"\n", client_certificate); |
669 | + } |
670 | + if (auth->client_key) { |
671 | +- g_string_append_printf(s, " private_key=\"%s\"\n", auth->client_key); |
672 | ++ g_autofree char* client_key = _netplan_scrub_string(auth->client_key); |
673 | ++ g_string_append_printf(s, " private_key=\"%s\"\n", client_key); |
674 | + } |
675 | + if (auth->client_key_password) { |
676 | + g_string_append_printf(s, " private_key_passwd=\"%s\"\n", auth->client_key_password); |
677 | + } |
678 | + if (auth->phase2_auth) { |
679 | +- g_string_append_printf(s, " phase2=\"auth=%s\"\n", auth->phase2_auth); |
680 | ++ g_autofree char* phase2_auth = _netplan_scrub_string(auth->phase2_auth); |
681 | ++ g_string_append_printf(s, " phase2=\"auth=%s\"\n", phase2_auth); |
682 | + } |
683 | + return TRUE; |
684 | + } |
685 | +@@ -1203,14 +1211,16 @@ write_wpa_conf(const NetplanNetDefinition* def, const char* rootdir, GError** er |
686 | + } |
687 | + /* available as of wpa_supplicant version 0.6.7 */ |
688 | + if (def->regulatory_domain) { |
689 | +- g_string_append_printf(s, "country=%s\n", def->regulatory_domain); |
690 | ++ g_autofree char* regdom = _netplan_scrub_string(def->regulatory_domain); |
691 | ++ g_string_append_printf(s, "country=%s\n", regdom); |
692 | + } |
693 | + NetplanWifiAccessPoint* ap; |
694 | + g_hash_table_iter_init(&iter, def->access_points); |
695 | + while (g_hash_table_iter_next(&iter, NULL, (gpointer) &ap)) { |
696 | + gchar* freq_config_str = ap->mode == NETPLAN_WIFI_MODE_ADHOC ? "frequency" : "freq_list"; |
697 | ++ g_autofree char* ssid = _netplan_scrub_string(ap->ssid); |
698 | + |
699 | +- g_string_append_printf(s, "network={\n ssid=\"%s\"\n", ap->ssid); |
700 | ++ g_string_append_printf(s, "network={\n ssid=P\"%s\"\n", ssid); |
701 | + if (ap->bssid) { |
702 | + g_string_append_printf(s, " bssid=%s\n", ap->bssid); |
703 | + } |
704 | +@@ -1257,7 +1267,7 @@ write_wpa_conf(const NetplanNetDefinition* def, const char* rootdir, GError** er |
705 | + |
706 | + /* wifi auth trumps netdef auth */ |
707 | + if (ap->has_auth) { |
708 | +- if (!append_wpa_auth_conf(s, &ap->auth, ap->ssid, error)) { |
709 | ++ if (!append_wpa_auth_conf(s, &ap->auth, ssid, error)) { |
710 | + g_string_free(s, TRUE); |
711 | + return FALSE; |
712 | + } |
713 | +diff --git a/src/nm.c b/src/nm.c |
714 | +index d5dad98..a605c38 100644 |
715 | +--- a/src/nm.c |
716 | ++++ b/src/nm.c |
717 | +@@ -1081,28 +1081,30 @@ netplan_state_finish_nm_write( |
718 | + GString *tmp = NULL; |
719 | + guint unmanaged = nd->backend == NETPLAN_BACKEND_NM ? 0 : 1; |
720 | + |
721 | ++ g_autofree char* netdef_id = _netplan_scrub_string(nd->id); |
722 | + /* Special case: manage or ignore any device of given type on empty "match: {}" stanza */ |
723 | + if (nd->has_match && !nd->match.driver && !nd->match.mac && !nd->match.original_name) { |
724 | + nm_type = type_str(nd); |
725 | + g_assert(nm_type); |
726 | + g_string_append_printf(nm_conf, "[device-netplan.%s.%s]\nmatch-device=type:%s\n" |
727 | + "managed=%d\n\n", netplan_def_type_name(nd->type), |
728 | +- nd->id, nm_type, !unmanaged); |
729 | ++ netdef_id, nm_type, !unmanaged); |
730 | + } |
731 | + /* Normal case: manage or ignore devices by specific udev rules */ |
732 | + else { |
733 | + const gchar *prefix = "SUBSYSTEM==\"net\", ACTION==\"add|change|move\","; |
734 | + const gchar *suffix = nd->backend == NETPLAN_BACKEND_NM ? " ENV{NM_UNMANAGED}=\"0\"\n" : " ENV{NM_UNMANAGED}=\"1\"\n"; |
735 | + g_string_append_printf(udev_rules, "# netplan: network.%s.%s (on NetworkManager %s)\n", |
736 | +- netplan_def_type_name(nd->type), nd->id, |
737 | ++ netplan_def_type_name(nd->type), netdef_id, |
738 | + unmanaged ? "deny-list" : "allow-list"); |
739 | + /* Match by explicit interface name, if possible */ |
740 | + if (nd->set_name) { |
741 | + // simple case: explicit new interface name |
742 | +- g_string_append_printf(udev_rules, "%s ENV{ID_NET_NAME}==\"%s\",%s", prefix, nd->set_name, suffix); |
743 | ++ g_autofree char* set_name = _netplan_scrub_string(nd->set_name); |
744 | ++ g_string_append_printf(udev_rules, "%s ENV{ID_NET_NAME}==\"%s\",%s", prefix, set_name, suffix); |
745 | + } else if (!nd->has_match) { |
746 | + // simple case: explicit netplan ID is interface name |
747 | +- g_string_append_printf(udev_rules, "%s ENV{ID_NET_NAME}==\"%s\",%s", prefix, nd->id, suffix); |
748 | ++ g_string_append_printf(udev_rules, "%s ENV{ID_NET_NAME}==\"%s\",%s", prefix, netdef_id, suffix); |
749 | + } |
750 | + /* Also, match by explicit (new) MAC, if available */ |
751 | + if (nd->set_mac) { |
752 | +@@ -1118,9 +1120,10 @@ netplan_state_finish_nm_write( |
753 | + // match on original name glob |
754 | + // TODO: maybe support matching on multiple name globs in the future (like drivers) |
755 | + g_string_append(udev_rules, prefix); |
756 | +- if (nd->match.original_name) |
757 | +- g_string_append_printf(udev_rules, " ENV{ID_NET_NAME}==\"%s\",", nd->match.original_name); |
758 | +- |
759 | ++ if (nd->match.original_name) { |
760 | ++ g_autofree char* original_name = _netplan_scrub_string(nd->match.original_name); |
761 | ++ g_string_append_printf(udev_rules, " ENV{ID_NET_NAME}==\"%s\",", original_name); |
762 | ++ } |
763 | + // match on (explicit) MAC address. Yes this would be unique on its own, but we |
764 | + // keep it within the "full match" to make the logic more comprehensible. |
765 | + if (nd->match.mac) { |
766 | +@@ -1138,7 +1141,9 @@ netplan_state_finish_nm_write( |
767 | + g_strfreev(split); |
768 | + } else |
769 | + drivers = g_strdup(nd->match.driver); |
770 | +- g_string_append_printf(udev_rules, " ENV{ID_NET_DRIVER}==\"%s\",", drivers); |
771 | ++ |
772 | ++ g_autofree char* escaped_drivers = _netplan_scrub_string(drivers); |
773 | ++ g_string_append_printf(udev_rules, " ENV{ID_NET_DRIVER}==\"%s\",", escaped_drivers); |
774 | + g_free(drivers); |
775 | + } |
776 | + g_string_append(udev_rules, suffix); |
777 | +diff --git a/src/parse.c b/src/parse.c |
778 | +index d70b564..e37648d 100644 |
779 | +--- a/src/parse.c |
780 | ++++ b/src/parse.c |
781 | +@@ -59,6 +59,15 @@ extern NetplanState global_state; |
782 | + |
783 | + NetplanParser global_parser = {0}; |
784 | + |
785 | ++/* |
786 | ++ * We use g_strescape to escape control characters from the input. |
787 | ++ * Besides control characters, g_strescape will also escape double quotes and backslashes. |
788 | ++ * Quotes are escaped at configuration generation time as needed, as they might be part of passwords for example. |
789 | ++ * Escaping backslashes in the parser affects "netplan set" as it will always escape \'s from |
790 | ++ * the input and update YAMLs with all the \'s escaped again. |
791 | ++*/ |
792 | ++static char* STRESCAPE_EXCEPTIONS = "\"\\"; |
793 | ++ |
794 | + static gboolean |
795 | + insert_kv_into_hash(void *key, void *value, void *hash); |
796 | + |
797 | +@@ -365,7 +374,7 @@ handle_generic_str(NetplanParser* npp, yaml_node_t* node, void* entryptr, const |
798 | + guint offset = GPOINTER_TO_UINT(data); |
799 | + char** dest = (char**) ((void*) entryptr + offset); |
800 | + g_free(*dest); |
801 | +- *dest = g_strdup(scalar(node)); |
802 | ++ *dest = g_strescape(scalar(node), STRESCAPE_EXCEPTIONS); |
803 | + mark_data_as_dirty(npp, dest); |
804 | + return TRUE; |
805 | + } |
806 | +@@ -480,22 +489,25 @@ handle_generic_map(NetplanParser *npp, yaml_node_t* node, const char* key_prefix |
807 | + assert_type(npp, key, YAML_SCALAR_NODE); |
808 | + assert_type(npp, value, YAML_SCALAR_NODE); |
809 | + |
810 | ++ g_autofree char* escaped_key = g_strescape(scalar(key), STRESCAPE_EXCEPTIONS); |
811 | ++ g_autofree char* escaped_value = g_strescape(scalar(value), STRESCAPE_EXCEPTIONS); |
812 | ++ |
813 | + if (key_prefix && npp->null_fields) { |
814 | + g_autofree char* full_key = NULL; |
815 | +- full_key = g_strdup_printf("%s\t%s", key_prefix, key->data.scalar.value); |
816 | ++ full_key = g_strdup_printf("%s\t%s", key_prefix, escaped_key); |
817 | + if (g_hash_table_contains(npp->null_fields, full_key)) |
818 | + continue; |
819 | + } |
820 | + |
821 | + char* stored_value = NULL; |
822 | +- if (g_hash_table_lookup_extended(*map, scalar(key), NULL, (void**)&stored_value)) { |
823 | ++ if (g_hash_table_lookup_extended(*map, escaped_key, NULL, (void**)&stored_value)) { |
824 | + /* We can safely skip this if it is the exact key/value match |
825 | + * (probably caused by multi-pass processing) */ |
826 | +- if (g_strcmp0(stored_value, scalar(value)) == 0) |
827 | ++ if (g_strcmp0(stored_value, escaped_value) == 0) |
828 | + continue; |
829 | +- return yaml_error(npp, node, error, "duplicate map entry '%s'", scalar(key)); |
830 | ++ return yaml_error(npp, node, error, "duplicate map entry '%s'", escaped_key); |
831 | + } else |
832 | +- g_hash_table_insert(*map, g_strdup(scalar(key)), g_strdup(scalar(value))); |
833 | ++ g_hash_table_insert(*map, g_strdup(escaped_key), g_strdup(escaped_value)); |
834 | + } |
835 | + mark_data_as_dirty(npp, map); |
836 | + |
837 | +@@ -518,20 +530,26 @@ handle_generic_datalist(NetplanParser *npp, yaml_node_t* node, const char* key_p |
838 | + for (yaml_node_pair_t* entry = node->data.mapping.pairs.start; entry < node->data.mapping.pairs.top; entry++) { |
839 | + yaml_node_t* key, *value; |
840 | + g_autofree char* full_key = NULL; |
841 | ++ g_autofree char* escaped_key = NULL; |
842 | ++ g_autofree char* escaped_value = NULL; |
843 | + |
844 | + key = yaml_document_get_node(&npp->doc, entry->key); |
845 | + value = yaml_document_get_node(&npp->doc, entry->value); |
846 | + |
847 | + assert_type(npp, key, YAML_SCALAR_NODE); |
848 | + assert_type(npp, value, YAML_SCALAR_NODE); |
849 | ++ |
850 | ++ escaped_key = g_strescape(scalar(key), STRESCAPE_EXCEPTIONS); |
851 | ++ escaped_value = g_strescape(scalar(value), STRESCAPE_EXCEPTIONS); |
852 | ++ |
853 | + if (npp->null_fields && key_prefix) { |
854 | +- full_key = g_strdup_printf("%s\t%s", key_prefix, scalar(key)); |
855 | ++ full_key = g_strdup_printf("%s\t%s", key_prefix, escaped_key); |
856 | + if (g_hash_table_contains(npp->null_fields, full_key)) |
857 | + continue; |
858 | + } |
859 | + |
860 | +- g_datalist_id_set_data_full(list, g_quark_from_string(scalar(key)), |
861 | +- g_strdup(scalar(value)), g_free); |
862 | ++ g_datalist_id_set_data_full(list, g_quark_from_string(escaped_key), |
863 | ++ g_strdup(escaped_value), g_free); |
864 | + } |
865 | + mark_data_as_dirty(npp, list); |
866 | + |
867 | +@@ -864,13 +882,14 @@ handle_match_driver(NetplanParser* npp, yaml_node_t* node, __unused const char* |
868 | + for (yaml_node_item_t *iter = node->data.sequence.items.start; iter < node->data.sequence.items.top; iter++) { |
869 | + elem = yaml_document_get_node(&npp->doc, *iter); |
870 | + assert_type(npp, elem, YAML_SCALAR_NODE); |
871 | +- if (g_strrstr(scalar(elem), " ")) |
872 | ++ g_autofree char* escaped_elem = g_strescape(scalar(elem), STRESCAPE_EXCEPTIONS); |
873 | ++ if (g_strrstr(escaped_elem, " ")) |
874 | + return yaml_error(npp, node, error, "A 'driver' glob cannot contain whitespace"); |
875 | + |
876 | + if (!sequence) |
877 | +- sequence = g_string_new(scalar(elem)); |
878 | ++ sequence = g_string_new(escaped_elem); |
879 | + else |
880 | +- g_string_append_printf(sequence, "\t%s", scalar(elem)); /* tab separated */ |
881 | ++ g_string_append_printf(sequence, "\t%s", escaped_elem); /* tab separated */ |
882 | + } |
883 | + |
884 | + if (!sequence) |
885 | +@@ -902,7 +921,7 @@ handle_auth_str(NetplanParser* npp, yaml_node_t* node, const void* data, __unuse |
886 | + guint offset = GPOINTER_TO_UINT(data); |
887 | + char** dest = (char**) ((void*) npp->current.auth + offset); |
888 | + g_free(*dest); |
889 | +- *dest = g_strdup(scalar(node)); |
890 | ++ *dest = g_strescape(scalar(node), STRESCAPE_EXCEPTIONS); |
891 | + mark_data_as_dirty(npp, dest); |
892 | + return TRUE; |
893 | + } |
894 | +@@ -1050,7 +1069,7 @@ handle_access_point_password(NetplanParser* npp, yaml_node_t* node, __unused con |
895 | + |
896 | + access_point->auth.pmf_mode = NETPLAN_AUTH_PMF_MODE_OPTIONAL; |
897 | + g_free(access_point->auth.psk); |
898 | +- access_point->auth.psk = g_strdup(scalar(node)); |
899 | ++ access_point->auth.psk = g_strescape(scalar(node), STRESCAPE_EXCEPTIONS); |
900 | + return TRUE; |
901 | + } |
902 | + |
903 | +@@ -1434,6 +1453,7 @@ handle_wifi_access_points(NetplanParser* npp, yaml_node_t* node, const char* key |
904 | + for (yaml_node_pair_t* entry = node->data.mapping.pairs.start; entry < node->data.mapping.pairs.top; entry++) { |
905 | + NetplanWifiAccessPoint *access_point = NULL; |
906 | + g_autofree char* full_key = NULL; |
907 | ++ g_autofree char* escaped_key = NULL; |
908 | + yaml_node_t* key, *value; |
909 | + const gchar* ssid; |
910 | + |
911 | +@@ -1442,13 +1462,15 @@ handle_wifi_access_points(NetplanParser* npp, yaml_node_t* node, const char* key |
912 | + value = yaml_document_get_node(&npp->doc, entry->value); |
913 | + assert_type(npp, value, YAML_MAPPING_NODE); |
914 | + |
915 | ++ escaped_key = g_strescape(scalar(key), STRESCAPE_EXCEPTIONS); |
916 | ++ |
917 | + if (key_prefix && npp->null_fields) { |
918 | +- full_key = g_strdup_printf("%s\t%s", key_prefix, key->data.scalar.value); |
919 | ++ full_key = g_strdup_printf("%s\t%s", key_prefix, escaped_key); |
920 | + if (g_hash_table_contains(npp->null_fields, full_key)) |
921 | + continue; |
922 | + } |
923 | + |
924 | +- ssid = scalar(key); |
925 | ++ ssid = escaped_key; |
926 | + |
927 | + /* |
928 | + * Delete the access-point if it already exists in the netdef and let the new |
929 | +@@ -1606,15 +1628,16 @@ handle_nameservers_search(NetplanParser* npp, yaml_node_t* node, __unused const |
930 | + for (yaml_node_item_t *i = node->data.sequence.items.start; i < node->data.sequence.items.top; i++) { |
931 | + yaml_node_t *entry = yaml_document_get_node(&npp->doc, *i); |
932 | + assert_type(npp, entry, YAML_SCALAR_NODE); |
933 | ++ g_autofree char* escaped_entry = g_strescape(scalar(entry), STRESCAPE_EXCEPTIONS); |
934 | + |
935 | + if (!npp->current.netdef->search_domains) |
936 | + npp->current.netdef->search_domains = g_array_new(FALSE, FALSE, sizeof(char*)); |
937 | + |
938 | +- if (!is_string_in_array(npp->current.netdef->search_domains, scalar(entry))) { |
939 | +- char* s = g_strdup(scalar(entry)); |
940 | ++ if (!is_string_in_array(npp->current.netdef->search_domains, escaped_entry)) { |
941 | ++ char* s = g_strdup(escaped_entry); |
942 | + g_array_append_val(npp->current.netdef->search_domains, s); |
943 | + } else { |
944 | +- g_debug("%s: Search domain '%s' has already been added", npp->current.netdef->id, scalar(entry)); |
945 | ++ g_debug("%s: Search domain '%s' has already been added", npp->current.netdef->id, escaped_entry); |
946 | + } |
947 | + } |
948 | + mark_data_as_dirty(npp, &npp->current.netdef->search_domains); |
949 | +@@ -2720,19 +2743,19 @@ handle_ovs_bridge_controller_addresses(NetplanParser* npp, yaml_node_t* node, __ |
950 | + |
951 | + /* Format: [p]unix:file */ |
952 | + if (is_unix && vec[1] != NULL && vec[2] == NULL) { |
953 | +- char* s = g_strdup(scalar(entry)); |
954 | ++ char* s = g_strescape(scalar(entry), STRESCAPE_EXCEPTIONS); |
955 | + g_array_append_val(npp->current.netdef->ovs_settings.controller.addresses, s); |
956 | + g_strfreev(vec); |
957 | + continue; |
958 | + /* Format tcp:host[:port] or ssl:host[:port] */ |
959 | + } else if (is_host && validate_ovs_target(TRUE, vec[1])) { |
960 | +- char* s = g_strdup(scalar(entry)); |
961 | ++ char* s = g_strescape(scalar(entry), STRESCAPE_EXCEPTIONS); |
962 | + g_array_append_val(npp->current.netdef->ovs_settings.controller.addresses, s); |
963 | + g_strfreev(vec); |
964 | + continue; |
965 | + /* Format ptcp:[port][:host] or pssl:[port][:host] */ |
966 | + } else if (is_port && validate_ovs_target(FALSE, vec[1])) { |
967 | +- char* s = g_strdup(scalar(entry)); |
968 | ++ char* s = g_strescape(scalar(entry), STRESCAPE_EXCEPTIONS); |
969 | + g_array_append_val(npp->current.netdef->ovs_settings.controller.addresses, s); |
970 | + g_strfreev(vec); |
971 | + continue; |
972 | +@@ -3044,6 +3067,8 @@ handle_network_ovs_settings_global_ports(NetplanParser* npp, yaml_node_t* node, |
973 | + NetplanNetDefinition *component2 = NULL; |
974 | + |
975 | + for (yaml_node_item_t *iter = node->data.sequence.items.start; iter < node->data.sequence.items.top; iter++) { |
976 | ++ g_autofree char* escaped_port = NULL; |
977 | ++ g_autofree char* escaped_peer = NULL; |
978 | + pair = yaml_document_get_node(&npp->doc, *iter); |
979 | + assert_type(npp, pair, YAML_SEQUENCE_NODE); |
980 | + |
981 | +@@ -3061,11 +3086,14 @@ handle_network_ovs_settings_global_ports(NetplanParser* npp, yaml_node_t* node, |
982 | + if (!g_strcmp0(scalar(port), scalar(peer))) |
983 | + return yaml_error(npp, peer, error, "Open vSwitch patch ports must be of different name"); |
984 | + |
985 | ++ escaped_port = g_strescape(scalar(port), STRESCAPE_EXCEPTIONS); |
986 | ++ escaped_peer = g_strescape(scalar(peer), STRESCAPE_EXCEPTIONS); |
987 | ++ |
988 | + /* Create port 1 netdef */ |
989 | +- component1 = npp->parsed_defs ? g_hash_table_lookup(npp->parsed_defs, scalar(port)) : NULL; |
990 | ++ component1 = npp->parsed_defs ? g_hash_table_lookup(npp->parsed_defs, escaped_port) : NULL; |
991 | + if (!component1) { |
992 | +- component1 = netplan_netdef_new(npp, scalar(port), NETPLAN_DEF_TYPE_PORT, NETPLAN_BACKEND_OVS); |
993 | +- if (g_hash_table_remove(npp->missing_id, scalar(port))) |
994 | ++ component1 = netplan_netdef_new(npp, escaped_port, NETPLAN_DEF_TYPE_PORT, NETPLAN_BACKEND_OVS); |
995 | ++ if (g_hash_table_remove(npp->missing_id, escaped_port)) |
996 | + npp->missing_ids_found++; |
997 | + } |
998 | + |
999 | +@@ -3076,15 +3104,15 @@ handle_network_ovs_settings_global_ports(NetplanParser* npp, yaml_node_t* node, |
1000 | + component1->filepath = g_strdup(npp->current.filepath); |
1001 | + } |
1002 | + |
1003 | +- if (component1->peer && g_strcmp0(component1->peer, scalar(peer))) |
1004 | ++ if (component1->peer && g_strcmp0(component1->peer, escaped_peer)) |
1005 | + return yaml_error(npp, port, error, "Open vSwitch port '%s' is already assigned to peer '%s'", |
1006 | + component1->id, component1->peer); |
1007 | + |
1008 | + /* Create port 2 (peer) netdef */ |
1009 | +- component2 = npp->parsed_defs ? g_hash_table_lookup(npp->parsed_defs, scalar(peer)) : NULL; |
1010 | ++ component2 = npp->parsed_defs ? g_hash_table_lookup(npp->parsed_defs, escaped_peer) : NULL; |
1011 | + if (!component2) { |
1012 | +- component2 = netplan_netdef_new(npp, scalar(peer), NETPLAN_DEF_TYPE_PORT, NETPLAN_BACKEND_OVS); |
1013 | +- if (g_hash_table_remove(npp->missing_id, scalar(peer))) |
1014 | ++ component2 = netplan_netdef_new(npp, escaped_peer, NETPLAN_DEF_TYPE_PORT, NETPLAN_BACKEND_OVS); |
1015 | ++ if (g_hash_table_remove(npp->missing_id, escaped_peer)) |
1016 | + npp->missing_ids_found++; |
1017 | + } |
1018 | + |
1019 | +@@ -3095,16 +3123,16 @@ handle_network_ovs_settings_global_ports(NetplanParser* npp, yaml_node_t* node, |
1020 | + component2->filepath = g_strdup(npp->current.filepath); |
1021 | + } |
1022 | + |
1023 | +- if (component2->peer && g_strcmp0(component2->peer, scalar(port))) |
1024 | ++ if (component2->peer && g_strcmp0(component2->peer, escaped_port)) |
1025 | + return yaml_error(npp, peer, error, "Open vSwitch port '%s' is already assigned to peer '%s'", |
1026 | + component2->id, component2->peer); |
1027 | + |
1028 | + if (!component1->peer) { |
1029 | +- component1->peer = g_strdup(scalar(peer)); |
1030 | ++ component1->peer = g_strdup(escaped_peer); |
1031 | + component1->peer_link = component2; |
1032 | + } |
1033 | + if (!component2->peer) { |
1034 | +- component2->peer = g_strdup(scalar(port)); |
1035 | ++ component2->peer = g_strdup(escaped_port); |
1036 | + component2->peer_link = component1; |
1037 | + } |
1038 | + } |
1039 | +diff --git a/src/util-internal.h b/src/util-internal.h |
1040 | +index b30745b..f6f36fc 100644 |
1041 | +--- a/src/util-internal.h |
1042 | ++++ b/src/util-internal.h |
1043 | +@@ -182,3 +182,6 @@ _netplan_netdef_pertype_iter_next(struct netdef_pertype_iter* it); |
1044 | + |
1045 | + NETPLAN_INTERNAL void |
1046 | + _netplan_netdef_pertype_iter_free(struct netdef_pertype_iter* it); |
1047 | ++ |
1048 | ++gchar* |
1049 | ++_netplan_scrub_string(const char* content); |
1050 | +diff --git a/src/util.c b/src/util.c |
1051 | +index edecdab..d8d3a57 100644 |
1052 | +--- a/src/util.c |
1053 | ++++ b/src/util.c |
1054 | +@@ -1223,3 +1223,14 @@ _is_auth_key_management_psk(const NetplanAuthenticationSettings* auth) |
1055 | + return ( auth->key_management == NETPLAN_AUTH_KEY_MANAGEMENT_WPA_PSK |
1056 | + || auth->key_management == NETPLAN_AUTH_KEY_MANAGEMENT_WPA_SAE); |
1057 | + } |
1058 | ++ |
1059 | ++gchar* |
1060 | ++_netplan_scrub_string(const char* content) |
1061 | ++{ |
1062 | ++ GString* s = g_string_new(content); |
1063 | ++ |
1064 | ++ // Escape double quotes |
1065 | ++ g_string_replace(s, "\"", "\\\"", 0); |
1066 | ++ |
1067 | ++ return g_string_free(s, FALSE); |
1068 | ++} |
1069 | +diff --git a/tests/generator/test_auth.py b/tests/generator/test_auth.py |
1070 | +index d3d886c..bf0108a 100644 |
1071 | +--- a/tests/generator/test_auth.py |
1072 | ++++ b/tests/generator/test_auth.py |
1073 | +@@ -92,21 +92,21 @@ class TestNetworkd(TestBase): |
1074 | + self.assertIn('ctrl_interface=/run/wpa_supplicant', new_config) |
1075 | + self.assertIn(''' |
1076 | + network={ |
1077 | +- ssid="peer2peer" |
1078 | ++ ssid=P"peer2peer" |
1079 | + mode=1 |
1080 | + key_mgmt=NONE |
1081 | + } |
1082 | + ''', new_config) |
1083 | + self.assertIn(''' |
1084 | + network={ |
1085 | +- ssid="Luke's Home" |
1086 | ++ ssid=P"Luke's Home" |
1087 | + key_mgmt=WPA-PSK |
1088 | + psk="4lsos3kr1t" |
1089 | + } |
1090 | + ''', new_config) |
1091 | + self.assertIn(''' |
1092 | + network={ |
1093 | +- ssid="BobsHome" |
1094 | ++ ssid=P"BobsHome" |
1095 | + key_mgmt=WPA-PSK WPA-PSK-SHA256 SAE |
1096 | + ieee80211w=1 |
1097 | + psk=e03ce667c87bc81ca968d9120ca37f89eb09aec3c55b80386e5d772efd6b926e |
1098 | +@@ -114,14 +114,14 @@ network={ |
1099 | + ''', new_config) |
1100 | + self.assertIn(''' |
1101 | + network={ |
1102 | +- ssid="BillsHome" |
1103 | ++ ssid=P"BillsHome" |
1104 | + key_mgmt=WPA-PSK |
1105 | + psk=db3b0acf5653aeaddd5fe034fb9f07175b2864f847b005aaa2f09182d9411b04 |
1106 | + } |
1107 | + ''', new_config) |
1108 | + self.assertIn(''' |
1109 | + network={ |
1110 | +- ssid="workplace2" |
1111 | ++ ssid=P"workplace2" |
1112 | + key_mgmt=WPA-EAP |
1113 | + eap=PEAP |
1114 | + identity="joe@internal.example.com" |
1115 | +@@ -131,7 +131,7 @@ network={ |
1116 | + ''', new_config) |
1117 | + self.assertIn(''' |
1118 | + network={ |
1119 | +- ssid="workplace" |
1120 | ++ ssid=P"workplace" |
1121 | + key_mgmt=WPA-EAP |
1122 | + eap=TTLS |
1123 | + identity="joe@internal.example.com" |
1124 | +@@ -141,7 +141,7 @@ network={ |
1125 | + ''', new_config) |
1126 | + self.assertIn(''' |
1127 | + network={ |
1128 | +- ssid="workplacehashed" |
1129 | ++ ssid=P"workplacehashed" |
1130 | + key_mgmt=WPA-EAP |
1131 | + eap=TTLS |
1132 | + identity="joe@internal.example.com" |
1133 | +@@ -151,7 +151,7 @@ network={ |
1134 | + ''', new_config) |
1135 | + self.assertIn(''' |
1136 | + network={ |
1137 | +- ssid="customernet" |
1138 | ++ ssid=P"customernet" |
1139 | + key_mgmt=WPA-EAP |
1140 | + eap=TLS |
1141 | + identity="cert-joe@cust.example.com" |
1142 | +@@ -164,13 +164,13 @@ network={ |
1143 | + ''', new_config) |
1144 | + self.assertIn(''' |
1145 | + network={ |
1146 | +- ssid="opennet" |
1147 | ++ ssid=P"opennet" |
1148 | + key_mgmt=NONE |
1149 | + } |
1150 | + ''', new_config) |
1151 | + self.assertIn(''' |
1152 | + network={ |
1153 | +- ssid="Joe's Home" |
1154 | ++ ssid=P"Joe's Home" |
1155 | + key_mgmt=WPA-PSK WPA-PSK-SHA256 SAE |
1156 | + ieee80211w=1 |
1157 | + psk="s0s3kr1t" |
1158 | +diff --git a/tests/generator/test_common.py b/tests/generator/test_common.py |
1159 | +index 785f6f4..3aab3f7 100644 |
1160 | +--- a/tests/generator/test_common.py |
1161 | ++++ b/tests/generator/test_common.py |
1162 | +@@ -19,7 +19,7 @@ |
1163 | + import os |
1164 | + import textwrap |
1165 | + |
1166 | +-from .base import TestBase, ND_DHCP4, ND_DHCP6, ND_DHCPYES, ND_EMPTY, NM_MANAGED, NM_UNMANAGED |
1167 | ++from .base import UDEV_NO_MAC_RULE, TestBase, ND_DHCP4, ND_DHCP6, ND_DHCPYES, ND_EMPTY, NM_MANAGED, NM_UNMANAGED |
1168 | + |
1169 | + |
1170 | + class TestNetworkd(TestBase): |
1171 | +@@ -815,6 +815,18 @@ RouteMetric=100 |
1172 | + UseMTU=true |
1173 | + '''}) |
1174 | + |
1175 | ++ def test_nd_udev_rules_escaped(self): |
1176 | ++ self.generate('''network: |
1177 | ++ version: 2 |
1178 | ++ renderer: NetworkManager |
1179 | ++ ethernets: |
1180 | ++ def1: |
1181 | ++ match: |
1182 | ++ driver: "abc\\"xyz\\n0\\n\\n1" |
1183 | ++ set-name: "eth\\"\\n\\nxyz\\n0"''', skip_generated_yaml_validation=True) |
1184 | ++ |
1185 | ++ self.assert_networkd_udev({'def1.rules': (UDEV_NO_MAC_RULE % ('abc\\"xyz\\n0\\n\\n1', 'eth\\"\\n\\nxyz\\n0'))}) |
1186 | ++ |
1187 | + |
1188 | + class TestNetworkManager(TestBase): |
1189 | + |
1190 | +@@ -1320,6 +1332,28 @@ dns=8.8.8.8; |
1191 | + method=ignore |
1192 | + '''}) |
1193 | + |
1194 | ++ def test_nm_udev_rules_escaped(self): |
1195 | ++ self.generate('''network: |
1196 | ++ version: 2 |
1197 | ++ renderer: networkd |
1198 | ++ ethernets: |
1199 | ++ eth0: |
1200 | ++ match: |
1201 | ++ name: "eth\\"0" |
1202 | ++ dhcp4: true''') |
1203 | ++ self.assert_nm_udev(NM_UNMANAGED % 'eth\\"0') |
1204 | ++ |
1205 | ++ self.generate('''network: |
1206 | ++ version: 2 |
1207 | ++ renderer: networkd |
1208 | ++ ethernets: |
1209 | ++ eth0: |
1210 | ++ match: |
1211 | ++ name: "eth0" |
1212 | ++ set-name: "eth\\n0" |
1213 | ++ dhcp4: true''', skip_generated_yaml_validation=True) |
1214 | ++ self.assert_nm_udev(NM_UNMANAGED % 'eth\\n0' + NM_UNMANAGED % 'eth0') |
1215 | ++ |
1216 | + |
1217 | + class TestForwardDeclaration(TestBase): |
1218 | + |
1219 | +@@ -1782,13 +1816,13 @@ LinkLocalAddressing=ipv6 |
1220 | + self.assert_wpa_supplicant("wlan0", """ctrl_interface=/run/wpa_supplicant |
1221 | + |
1222 | + network={ |
1223 | +- ssid="mynewwifi" |
1224 | ++ ssid=P"mynewwifi" |
1225 | + key_mgmt=WPA-PSK WPA-PSK-SHA256 SAE |
1226 | + ieee80211w=1 |
1227 | + psk="aaaaaaaa" |
1228 | + } |
1229 | + network={ |
1230 | +- ssid="mywifi" |
1231 | ++ ssid=P"mywifi" |
1232 | + key_mgmt=WPA-PSK WPA-PSK-SHA256 SAE |
1233 | + ieee80211w=1 |
1234 | + psk="aaaaaaaa" |
1235 | +@@ -1819,7 +1853,7 @@ network={ |
1236 | + self.assert_wpa_supplicant("wlan0", """ctrl_interface=/run/wpa_supplicant |
1237 | + |
1238 | + network={ |
1239 | +- ssid="mywifi" |
1240 | ++ ssid=P"mywifi" |
1241 | + key_mgmt=WPA-PSK WPA-PSK-SHA256 SAE |
1242 | + ieee80211w=1 |
1243 | + psk="bbbbbbbb" |
1244 | +diff --git a/tests/generator/test_wifis.py b/tests/generator/test_wifis.py |
1245 | +index 610782a..9f7359b 100644 |
1246 | +--- a/tests/generator/test_wifis.py |
1247 | ++++ b/tests/generator/test_wifis.py |
1248 | +@@ -65,7 +65,7 @@ class TestNetworkd(TestBase): |
1249 | + with open(os.path.join(self.workdir.name, 'run/netplan/wpa-wl0.conf')) as f: |
1250 | + new_config = f.read() |
1251 | + |
1252 | +- network = 'ssid="{}"\n freq_list='.format('band-no-channel2') |
1253 | ++ network = 'ssid=P"{}"\n freq_list='.format('band-no-channel2') |
1254 | + freqs_5GHz = [5610, 5310, 5620, 5320, 5630, 5640, 5340, 5035, 5040, 5045, 5055, 5060, 5660, 5680, 5670, 5080, 5690, |
1255 | + 5700, 5710, 5720, 5825, 5745, 5755, 5805, 5765, 5160, 5775, 5170, 5480, 5180, 5795, 5190, 5500, 5200, |
1256 | + 5510, 5210, 5520, 5220, 5530, 5230, 5540, 5240, 5550, 5250, 5560, 5260, 5570, 5270, 5580, 5280, 5590, |
1257 | +@@ -76,7 +76,7 @@ class TestNetworkd(TestBase): |
1258 | + for freq in freqs_5GHz: |
1259 | + self.assertRegex(new_config, '{}[ 0-9]*{}[ 0-9]*\n'.format(network, freq)) |
1260 | + |
1261 | +- network = 'ssid="{}"\n freq_list='.format('band-no-channel') |
1262 | ++ network = 'ssid=P"{}"\n freq_list='.format('band-no-channel') |
1263 | + freqs_24GHz = [2412, 2417, 2422, 2427, 2432, 2442, 2447, 2437, 2452, 2457, 2462, 2467, 2472, 2484] |
1264 | + freqs = new_config.split(network) |
1265 | + freqs = freqs[1].split('\n')[0] |
1266 | +@@ -86,20 +86,20 @@ class TestNetworkd(TestBase): |
1267 | + |
1268 | + self.assertIn(''' |
1269 | + network={ |
1270 | +- ssid="channel-no-band" |
1271 | ++ ssid=P"channel-no-band" |
1272 | + key_mgmt=NONE |
1273 | + } |
1274 | + ''', new_config) |
1275 | + self.assertIn(''' |
1276 | + network={ |
1277 | +- ssid="peer2peer" |
1278 | ++ ssid=P"peer2peer" |
1279 | + mode=1 |
1280 | + key_mgmt=NONE |
1281 | + } |
1282 | + ''', new_config) |
1283 | + self.assertIn(''' |
1284 | + network={ |
1285 | +- ssid="hidden-y" |
1286 | ++ ssid=P"hidden-y" |
1287 | + scan_ssid=1 |
1288 | + key_mgmt=WPA-PSK WPA-PSK-SHA256 SAE |
1289 | + ieee80211w=1 |
1290 | +@@ -108,7 +108,7 @@ network={ |
1291 | + ''', new_config) |
1292 | + self.assertIn(''' |
1293 | + network={ |
1294 | +- ssid="hidden-n" |
1295 | ++ ssid=P"hidden-n" |
1296 | + key_mgmt=WPA-PSK WPA-PSK-SHA256 SAE |
1297 | + ieee80211w=1 |
1298 | + psk="5ecur1ty" |
1299 | +@@ -116,7 +116,7 @@ network={ |
1300 | + ''', new_config) |
1301 | + self.assertIn(''' |
1302 | + network={ |
1303 | +- ssid="workplace" |
1304 | ++ ssid=P"workplace" |
1305 | + bssid=de:ad:be:ef:ca:fe |
1306 | + freq_list=5500 |
1307 | + key_mgmt=WPA-PSK WPA-PSK-SHA256 SAE |
1308 | +@@ -126,7 +126,7 @@ network={ |
1309 | + ''', new_config) |
1310 | + self.assertIn(''' |
1311 | + network={ |
1312 | +- ssid="Joe's Home" |
1313 | ++ ssid=P"Joe's Home" |
1314 | + bssid=00:11:22:33:44:55 |
1315 | + freq_list=2462 |
1316 | + key_mgmt=WPA-PSK WPA-PSK-SHA256 SAE |
1317 | +@@ -303,7 +303,7 @@ LinkLocalAddressing=ipv6 |
1318 | + self.assertIn(''' |
1319 | + wowlan_triggers=any disconnect magic_pkt gtk_rekey_failure eap_identity_req four_way_handshake rfkill_release |
1320 | + network={ |
1321 | +- ssid="homenet" |
1322 | ++ ssid=P"homenet" |
1323 | + key_mgmt=NONE |
1324 | + } |
1325 | + ''', new_config) |
1326 | +@@ -336,7 +336,7 @@ LinkLocalAddressing=ipv6 |
1327 | + new_config = f.read() |
1328 | + self.assertIn(''' |
1329 | + network={ |
1330 | +- ssid="homenet" |
1331 | ++ ssid=P"homenet" |
1332 | + key_mgmt=NONE |
1333 | + } |
1334 | + ''', new_config) |
1335 | +@@ -360,7 +360,7 @@ network={ |
1336 | + self.assert_wpa_supplicant("wl0", """ctrl_interface=/run/wpa_supplicant |
1337 | + |
1338 | + network={ |
1339 | +- ssid="homenet" |
1340 | ++ ssid=P"homenet" |
1341 | + key_mgmt=SAE |
1342 | + ieee80211w=2 |
1343 | + psk="********" |
1344 | +@@ -387,7 +387,7 @@ network={ |
1345 | + self.assert_wpa_supplicant("wl0", """ctrl_interface=/run/wpa_supplicant |
1346 | + |
1347 | + network={ |
1348 | +- ssid="homenet" |
1349 | ++ ssid=P"homenet" |
1350 | + key_mgmt=WPA-EAP WPA-EAP-SHA256 |
1351 | + eap=TLS |
1352 | + ieee80211w=1 |
1353 | +@@ -420,7 +420,7 @@ network={ |
1354 | + self.assert_wpa_supplicant("wl0", """ctrl_interface=/run/wpa_supplicant |
1355 | + |
1356 | + network={ |
1357 | +- ssid="homenet" |
1358 | ++ ssid=P"homenet" |
1359 | + key_mgmt=WPA-EAP-SUITE-B-192 |
1360 | + eap=TLS |
1361 | + ieee80211w=2 |
1362 | +@@ -449,7 +449,7 @@ network={ |
1363 | + self.assert_wpa_supplicant("wl0", """ctrl_interface=/run/wpa_supplicant |
1364 | + |
1365 | + network={ |
1366 | +- ssid="homenet" |
1367 | ++ ssid=P"homenet" |
1368 | + key_mgmt=IEEE8021X |
1369 | + eap=LEAP |
1370 | + identity="some-id" |
1371 | +@@ -473,7 +473,7 @@ network={ |
1372 | + self.assert_wpa_supplicant("wl0", """ctrl_interface=/run/wpa_supplicant |
1373 | + |
1374 | + network={ |
1375 | +- ssid="homenet" |
1376 | ++ ssid=P"homenet" |
1377 | + key_mgmt=IEEE8021X |
1378 | + eap=PWD |
1379 | + identity="some-id" |
1380 | +@@ -498,7 +498,7 @@ network={ |
1381 | + self.assert_wpa_supplicant("wl0", """ctrl_interface=/run/wpa_supplicant |
1382 | + |
1383 | + network={ |
1384 | +- ssid="homenet" |
1385 | ++ ssid=P"homenet" |
1386 | + key_mgmt=WPA-EAP |
1387 | + eap=LEAP |
1388 | + ieee80211w=1 |
1389 | +@@ -508,6 +508,48 @@ network={ |
1390 | + } |
1391 | + """) |
1392 | + |
1393 | ++ def test_escaping_special_characters(self): |
1394 | ++ self.generate('''network: |
1395 | ++ version: 2 |
1396 | ++ wifis: |
1397 | ++ wl0: |
1398 | ++ regulatory-domain: "abc\\n\\n321\\n\\"123" |
1399 | ++ access-points: |
1400 | ++ "abc\\n\\n123\\"x\\ry\\bz": |
1401 | ++ password: "abc\\n\\n\\n\\"123" |
1402 | ++ auth: |
1403 | ++ key-management: eap |
1404 | ++ method: leap |
1405 | ++ anonymous-identity: "abc\\n\\n321\\n\\"123" |
1406 | ++ identity: "abc\\n\\n321\\n\\"123" |
1407 | ++ password: "abc\\n\\n\\n\\"123" |
1408 | ++ ca-certificate: "abc\\n\\n321\\n\\"123" |
1409 | ++ client-certificate: "abc\\n\\n321\\n\\"123" |
1410 | ++ client-key: "abc\\n\\n321\\n\\"123" |
1411 | ++ client-key-password: "abc\\n\\n321\\n\\"123" |
1412 | ++ phase2-auth: "abc\\n\\n321\\n\\"123" |
1413 | ++ ''', skip_generated_yaml_validation=True) |
1414 | ++ |
1415 | ++ self.assert_wpa_supplicant("wl0", """ctrl_interface=/run/wpa_supplicant |
1416 | ++ |
1417 | ++country=abc\\n\\n321\\n\\"123 |
1418 | ++network={ |
1419 | ++ ssid=P"abc\\n\\n123\\\"x\\ry\\bz" |
1420 | ++ key_mgmt=WPA-EAP |
1421 | ++ eap=LEAP |
1422 | ++ ieee80211w=1 |
1423 | ++ identity="abc\\n\\n321\\n\\\"123" |
1424 | ++ anonymous_identity="abc\\n\\n321\\n\\\"123" |
1425 | ++ psk="abc\\n\\n\\n\"123" |
1426 | ++ password="abc\\n\\n\\n\"123" |
1427 | ++ ca_cert="abc\\n\\n321\\n\\\"123" |
1428 | ++ client_cert="abc\\n\\n321\\n\\\"123" |
1429 | ++ private_key="abc\\n\\n321\\n\\\"123" |
1430 | ++ private_key_passwd="abc\\n\\n321\\n\"123" |
1431 | ++ phase2="auth=abc\\n\\n321\\n\\\"123" |
1432 | ++} |
1433 | ++""") |
1434 | ++ |
1435 | + |
1436 | + class TestNetworkManager(TestBase): |
1437 | + |
1438 | +@@ -790,7 +832,7 @@ mode=adhoc |
1439 | + self.assert_wpa_supplicant("wl0", """ctrl_interface=/run/wpa_supplicant |
1440 | + |
1441 | + network={ |
1442 | +- ssid="homenet" |
1443 | ++ ssid=P"homenet" |
1444 | + frequency=2442 |
1445 | + mode=1 |
1446 | + key_mgmt=WPA-PSK WPA-PSK-SHA256 SAE |
1447 | +@@ -814,7 +856,7 @@ network={ |
1448 | + self.assert_wpa_supplicant("wl0", """ctrl_interface=/run/wpa_supplicant |
1449 | + |
1450 | + network={ |
1451 | +- ssid="homenet" |
1452 | ++ ssid=P"homenet" |
1453 | + frequency=5035 |
1454 | + mode=1 |
1455 | + key_mgmt=WPA-PSK WPA-PSK-SHA256 SAE |
1456 | diff --git a/debian/patches/lp2066258/0015-backends-escape-file-paths.patch b/debian/patches/lp2066258/0015-backends-escape-file-paths.patch |
1457 | new file mode 100644 |
1458 | index 0000000..6898b75 |
1459 | --- /dev/null |
1460 | +++ b/debian/patches/lp2066258/0015-backends-escape-file-paths.patch |
1461 | @@ -0,0 +1,288 @@ |
1462 | +From: Danilo Egea Gondolfo <danilogondolfo@gmail.com> |
1463 | +Date: Thu, 23 May 2024 15:54:43 +0100 |
1464 | +Subject: backends: escape file paths |
1465 | + |
1466 | +Escape strings used to build paths with g_uri_escape_string(). |
1467 | +systemd_escape() could also be used but it has the downside of calling |
1468 | +an external program and, by default, it escapes dashes (which are |
1469 | +present in files generated from Network Manager for example). |
1470 | +--- |
1471 | + src/networkd.c | 13 +++++---- |
1472 | + src/nm.c | 5 ++-- |
1473 | + src/openvswitch.c | 19 ++++++------ |
1474 | + src/util.c | 7 +++-- |
1475 | + tests/generator/test_common.py | 66 ++++++++++++++++++++++++++++++++++++++++++ |
1476 | + tests/generator/test_ovs.py | 24 +++++++++++++++ |
1477 | + 6 files changed, 115 insertions(+), 19 deletions(-) |
1478 | + |
1479 | +diff --git a/src/networkd.c b/src/networkd.c |
1480 | +index ac54112..e66ab4c 100644 |
1481 | +--- a/src/networkd.c |
1482 | ++++ b/src/networkd.c |
1483 | +@@ -981,7 +981,8 @@ static void |
1484 | + write_rules_file(const NetplanNetDefinition* def, const char* rootdir) |
1485 | + { |
1486 | + GString* s = NULL; |
1487 | +- g_autofree char* path = g_strjoin(NULL, "run/udev/rules.d/99-netplan-", def->id, ".rules", NULL); |
1488 | ++ g_autofree char* escaped_netdef_id = g_uri_escape_string(def->id, NULL, TRUE); |
1489 | ++ g_autofree char* path = g_strjoin(NULL, "run/udev/rules.d/99-netplan-", escaped_netdef_id, ".rules", NULL); |
1490 | + |
1491 | + /* do we need to write a .rules file? |
1492 | + * It's only required for reliably setting the name of a physical device |
1493 | +@@ -1198,7 +1199,8 @@ write_wpa_conf(const NetplanNetDefinition* def, const char* rootdir, GError** er |
1494 | + { |
1495 | + GHashTableIter iter; |
1496 | + GString* s = g_string_new("ctrl_interface=/run/wpa_supplicant\n\n"); |
1497 | +- g_autofree char* path = g_strjoin(NULL, "run/netplan/wpa-", def->id, ".conf", NULL); |
1498 | ++ g_autofree char* escaped_netdef_id = g_uri_escape_string(def->id, NULL, TRUE); |
1499 | ++ g_autofree char* path = g_strjoin(NULL, "run/netplan/wpa-", escaped_netdef_id, ".conf", NULL); |
1500 | + |
1501 | + g_debug("%s: Creating wpa_supplicant configuration file %s", def->id, path); |
1502 | + if (def->type == NETPLAN_DEF_TYPE_WIFI) { |
1503 | +@@ -1310,7 +1312,8 @@ netplan_netdef_write_networkd( |
1504 | + GError** error) |
1505 | + { |
1506 | + /* TODO: make use of netplan_netdef_get_output_filename() */ |
1507 | +- g_autofree char* path_base = g_strjoin(NULL, "run/systemd/network/10-netplan-", def->id, NULL); |
1508 | ++ g_autofree char* escaped_netdef_id = g_uri_escape_string(def->id, NULL, TRUE); |
1509 | ++ g_autofree char* path_base = g_strjoin(NULL, "run/systemd/network/10-netplan-", escaped_netdef_id, NULL); |
1510 | + SET_OPT_OUT_PTR(has_been_written, FALSE); |
1511 | + |
1512 | + /* We want this for all backends when renaming, as *.link and *.rules files are |
1513 | +@@ -1332,8 +1335,8 @@ netplan_netdef_write_networkd( |
1514 | + } |
1515 | + |
1516 | + if (def->type == NETPLAN_DEF_TYPE_WIFI || def->has_auth) { |
1517 | +- g_autofree char* link = g_strjoin(NULL, rootdir ?: "", "/run/systemd/system/systemd-networkd.service.wants/netplan-wpa-", def->id, ".service", NULL); |
1518 | +- g_autofree char* slink = g_strjoin(NULL, "/run/systemd/system/netplan-wpa-", def->id, ".service", NULL); |
1519 | ++ g_autofree char* link = g_strjoin(NULL, rootdir ?: "", "/run/systemd/system/systemd-networkd.service.wants/netplan-wpa-", escaped_netdef_id, ".service", NULL); |
1520 | ++ g_autofree char* slink = g_strjoin(NULL, "/run/systemd/system/netplan-wpa-", escaped_netdef_id, ".service", NULL); |
1521 | + if (def->type == NETPLAN_DEF_TYPE_WIFI && def->has_match) { |
1522 | + g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_UNSUPPORTED, "ERROR: %s: networkd backend does not support wifi with match:, only by interface name\n", def->id); |
1523 | + return FALSE; |
1524 | +diff --git a/src/nm.c b/src/nm.c |
1525 | +index a605c38..380b7fd 100644 |
1526 | +--- a/src/nm.c |
1527 | ++++ b/src/nm.c |
1528 | +@@ -931,10 +931,11 @@ write_nm_conf_access_point(const NetplanNetDefinition* def, const char* rootdir, |
1529 | + g_datalist_foreach((GData**)&def->backend_settings.passthrough, write_fallback_key_value, kf); |
1530 | + } |
1531 | + |
1532 | ++ g_autofree char* escaped_netdef_id = g_uri_escape_string(def->id, NULL, TRUE); |
1533 | + if (ap) { |
1534 | + g_autofree char* escaped_ssid = g_uri_escape_string(ap->ssid, NULL, TRUE); |
1535 | + /* TODO: make use of netplan_netdef_get_output_filename() */ |
1536 | +- conf_path = g_strjoin(NULL, "run/NetworkManager/system-connections/netplan-", def->id, "-", escaped_ssid, ".nmconnection", NULL); |
1537 | ++ conf_path = g_strjoin(NULL, "run/NetworkManager/system-connections/netplan-", escaped_netdef_id, "-", escaped_ssid, ".nmconnection", NULL); |
1538 | + |
1539 | + g_key_file_set_string(kf, "wifi", "ssid", ap->ssid); |
1540 | + if (ap->mode < NETPLAN_WIFI_MODE_OTHER) |
1541 | +@@ -969,7 +970,7 @@ write_nm_conf_access_point(const NetplanNetDefinition* def, const char* rootdir, |
1542 | + } |
1543 | + } else { |
1544 | + /* TODO: make use of netplan_netdef_get_output_filename() */ |
1545 | +- conf_path = g_strjoin(NULL, "run/NetworkManager/system-connections/netplan-", def->id, ".nmconnection", NULL); |
1546 | ++ conf_path = g_strjoin(NULL, "run/NetworkManager/system-connections/netplan-", escaped_netdef_id, ".nmconnection", NULL); |
1547 | + if (def->has_auth) { |
1548 | + write_dot1x_auth_parameters(&def->auth, kf); |
1549 | + } |
1550 | +diff --git a/src/openvswitch.c b/src/openvswitch.c |
1551 | +index 276762e..b33769d 100644 |
1552 | +--- a/src/openvswitch.c |
1553 | ++++ b/src/openvswitch.c |
1554 | +@@ -32,9 +32,9 @@ |
1555 | + static gboolean |
1556 | + write_ovs_systemd_unit(const char* id, const GString* cmds, const char* rootdir, gboolean physical, gboolean cleanup, const char* dependency, GError** error) |
1557 | + { |
1558 | +- g_autofree gchar* id_escaped = NULL; |
1559 | +- g_autofree char* link = g_strjoin(NULL, rootdir ?: "", "/run/systemd/system/systemd-networkd.service.wants/netplan-ovs-", id, ".service", NULL); |
1560 | +- g_autofree char* path = g_strjoin(NULL, "/run/systemd/system/netplan-ovs-", id, ".service", NULL); |
1561 | ++ g_autofree char* escaped_netdef_id = g_uri_escape_string(id, NULL, TRUE); |
1562 | ++ g_autofree char* link = g_strjoin(NULL, rootdir ?: "", "/run/systemd/system/systemd-networkd.service.wants/netplan-ovs-", escaped_netdef_id, ".service", NULL); |
1563 | ++ g_autofree char* path = g_strjoin(NULL, "/run/systemd/system/netplan-ovs-", escaped_netdef_id, ".service", NULL); |
1564 | + |
1565 | + GString* s = g_string_new("[Unit]\n"); |
1566 | + g_string_append_printf(s, "Description=OpenVSwitch configuration for %s\n", id); |
1567 | +@@ -43,9 +43,8 @@ write_ovs_systemd_unit(const char* id, const GString* cmds, const char* rootdir, |
1568 | + g_string_append_printf(s, "Wants=ovsdb-server.service\n"); |
1569 | + g_string_append_printf(s, "After=ovsdb-server.service\n"); |
1570 | + if (physical) { |
1571 | +- id_escaped = systemd_escape((char*) id); |
1572 | +- g_string_append_printf(s, "Requires=sys-subsystem-net-devices-%s.device\n", id_escaped); |
1573 | +- g_string_append_printf(s, "After=sys-subsystem-net-devices-%s.device\n", id_escaped); |
1574 | ++ g_string_append_printf(s, "Requires=sys-subsystem-net-devices-%s.device\n", escaped_netdef_id); |
1575 | ++ g_string_append_printf(s, "After=sys-subsystem-net-devices-%s.device\n", escaped_netdef_id); |
1576 | + } |
1577 | + if (!cleanup) { |
1578 | + g_string_append_printf(s, "After=netplan-ovs-cleanup.service\n"); |
1579 | +@@ -55,8 +54,9 @@ write_ovs_systemd_unit(const char* id, const GString* cmds, const char* rootdir, |
1580 | + } |
1581 | + g_string_append(s, "Before=network.target\nWants=network.target\n"); |
1582 | + if (dependency) { |
1583 | +- g_string_append_printf(s, "Requires=netplan-ovs-%s.service\n", dependency); |
1584 | +- g_string_append_printf(s, "After=netplan-ovs-%s.service\n", dependency); |
1585 | ++ g_autofree char* escaped_dependency = g_uri_escape_string(dependency, NULL, TRUE); |
1586 | ++ g_string_append_printf(s, "Requires=netplan-ovs-%s.service\n", escaped_dependency); |
1587 | ++ g_string_append_printf(s, "After=netplan-ovs-%s.service\n", escaped_dependency); |
1588 | + } |
1589 | + |
1590 | + g_string_append(s, "\n[Service]\nType=oneshot\nTimeoutStartSec=10s\n"); |
1591 | +@@ -321,6 +321,7 @@ netplan_netdef_write_ovs(const NetplanState* np_state, const NetplanNetDefinitio |
1592 | + gchar* dependency = NULL; |
1593 | + const char* type = netplan_type_to_table_name(def->type); |
1594 | + g_autofree char* base_config_path = NULL; |
1595 | ++ g_autofree char* escaped_netdef_id = g_uri_escape_string(def->id, NULL, TRUE); |
1596 | + char* value = NULL; |
1597 | + const NetplanOVSSettings* settings = &np_state->ovs_settings; |
1598 | + |
1599 | +@@ -411,7 +412,7 @@ netplan_netdef_write_ovs(const NetplanState* np_state, const NetplanNetDefinitio |
1600 | + |
1601 | + /* Try writing out a base config */ |
1602 | + /* TODO: make use of netplan_netdef_get_output_filename() */ |
1603 | +- base_config_path = g_strjoin(NULL, "run/systemd/network/10-netplan-", def->id, NULL); |
1604 | ++ base_config_path = g_strjoin(NULL, "run/systemd/network/10-netplan-", escaped_netdef_id, NULL); |
1605 | + if (!netplan_netdef_write_network_file(np_state, def, rootdir, base_config_path, has_been_written, error)) |
1606 | + return FALSE; |
1607 | + } else { |
1608 | +diff --git a/src/util.c b/src/util.c |
1609 | +index d8d3a57..a3dae98 100644 |
1610 | +--- a/src/util.c |
1611 | ++++ b/src/util.c |
1612 | +@@ -691,17 +691,18 @@ ssize_t |
1613 | + netplan_netdef_get_output_filename(const NetplanNetDefinition* netdef, const char* ssid, char* out_buffer, size_t out_buf_size) |
1614 | + { |
1615 | + g_autofree gchar* conf_path = NULL; |
1616 | ++ g_autofree char* escaped_netdef_id = g_uri_escape_string(netdef->id, NULL, TRUE); |
1617 | + |
1618 | + if (netdef->backend == NETPLAN_BACKEND_NM) { |
1619 | + if (ssid) { |
1620 | + g_autofree char* escaped_ssid = g_uri_escape_string(ssid, NULL, TRUE); |
1621 | +- conf_path = g_strjoin(NULL, "/run/NetworkManager/system-connections/netplan-", netdef->id, "-", escaped_ssid, ".nmconnection", NULL); |
1622 | ++ conf_path = g_strjoin(NULL, "/run/NetworkManager/system-connections/netplan-", escaped_netdef_id, "-", escaped_ssid, ".nmconnection", NULL); |
1623 | + } else { |
1624 | +- conf_path = g_strjoin(NULL, "/run/NetworkManager/system-connections/netplan-", netdef->id, ".nmconnection", NULL); |
1625 | ++ conf_path = g_strjoin(NULL, "/run/NetworkManager/system-connections/netplan-", escaped_netdef_id, ".nmconnection", NULL); |
1626 | + } |
1627 | + |
1628 | + } else if (netdef->backend == NETPLAN_BACKEND_NETWORKD || netdef->backend == NETPLAN_BACKEND_OVS) { |
1629 | +- conf_path = g_strjoin(NULL, "/run/systemd/network/10-netplan-", netdef->id, ".network", NULL); |
1630 | ++ conf_path = g_strjoin(NULL, "/run/systemd/network/10-netplan-", escaped_netdef_id, ".network", NULL); |
1631 | + } |
1632 | + |
1633 | + if (conf_path) |
1634 | +diff --git a/tests/generator/test_common.py b/tests/generator/test_common.py |
1635 | +index 3aab3f7..f145104 100644 |
1636 | +--- a/tests/generator/test_common.py |
1637 | ++++ b/tests/generator/test_common.py |
1638 | +@@ -827,6 +827,47 @@ UseMTU=true |
1639 | + |
1640 | + self.assert_networkd_udev({'def1.rules': (UDEV_NO_MAC_RULE % ('abc\\"xyz\\n0\\n\\n1', 'eth\\"\\n\\nxyz\\n0'))}) |
1641 | + |
1642 | ++ def test_nd_file_paths_escaped(self): |
1643 | ++ self.generate('''network: |
1644 | ++ version: 2 |
1645 | ++ ethernets: |
1646 | ++ "abc/../../xyz0": |
1647 | ++ match: |
1648 | ++ driver: "drv" |
1649 | ++ set-name: "eth123"''') |
1650 | ++ |
1651 | ++ self.assert_networkd_udev({'abc%2F..%2F..%2Fxyz0.rules': (UDEV_NO_MAC_RULE % ('drv', 'eth123'))}) |
1652 | ++ self.assert_networkd({'abc%2F..%2F..%2Fxyz0.network': '''[Match]\nDriver=drv |
1653 | ++Name=eth123 |
1654 | ++ |
1655 | ++[Network] |
1656 | ++LinkLocalAddressing=ipv6 |
1657 | ++''', |
1658 | ++ 'abc%2F..%2F..%2Fxyz0.link': '''[Match]\nDriver=drv\n |
1659 | ++[Link] |
1660 | ++Name=eth123 |
1661 | ++WakeOnLan=off |
1662 | ++'''}) |
1663 | ++ |
1664 | ++ self.generate('''network: |
1665 | ++ version: 2 |
1666 | ++ wifis: |
1667 | ++ "abc/../../xyz0": |
1668 | ++ dhcp4: true |
1669 | ++ access-points: |
1670 | ++ "mywifi": |
1671 | ++ password: "aaaaaaaa"''') |
1672 | ++ |
1673 | ++ self.assert_wpa_supplicant("abc%2F..%2F..%2Fxyz0", """ctrl_interface=/run/wpa_supplicant |
1674 | ++ |
1675 | ++network={ |
1676 | ++ ssid=P"mywifi" |
1677 | ++ key_mgmt=WPA-PSK WPA-PSK-SHA256 SAE |
1678 | ++ ieee80211w=1 |
1679 | ++ psk="aaaaaaaa" |
1680 | ++} |
1681 | ++""") |
1682 | ++ |
1683 | + |
1684 | + class TestNetworkManager(TestBase): |
1685 | + |
1686 | +@@ -1354,6 +1395,31 @@ method=ignore |
1687 | + dhcp4: true''', skip_generated_yaml_validation=True) |
1688 | + self.assert_nm_udev(NM_UNMANAGED % 'eth\\n0' + NM_UNMANAGED % 'eth0') |
1689 | + |
1690 | ++ def test_nm_file_paths_escaped(self): |
1691 | ++ self.generate('''network: |
1692 | ++ version: 2 |
1693 | ++ renderer: NetworkManager |
1694 | ++ ethernets: |
1695 | ++ "abc/../../xyz0": |
1696 | ++ match: |
1697 | ++ driver: "drv" |
1698 | ++ set-name: "eth123"''') |
1699 | ++ |
1700 | ++ self.assert_nm({'abc%2F..%2F..%2Fxyz0': '''[connection] |
1701 | ++id=netplan-abc/../../xyz0 |
1702 | ++type=ethernet |
1703 | ++interface-name=eth123 |
1704 | ++ |
1705 | ++[ethernet] |
1706 | ++wake-on-lan=0 |
1707 | ++ |
1708 | ++[ipv4] |
1709 | ++method=link-local |
1710 | ++ |
1711 | ++[ipv6] |
1712 | ++method=ignore |
1713 | ++'''}) |
1714 | ++ |
1715 | + |
1716 | + class TestForwardDeclaration(TestBase): |
1717 | + |
1718 | +diff --git a/tests/generator/test_ovs.py b/tests/generator/test_ovs.py |
1719 | +index 1dad3cb..e60d497 100644 |
1720 | +--- a/tests/generator/test_ovs.py |
1721 | ++++ b/tests/generator/test_ovs.py |
1722 | +@@ -1104,3 +1104,27 @@ ExecStart=/usr/bin/ovs-vsctl set Bridge br123 external-ids:netplan/global/set-co |
1723 | + - [portname, portname] |
1724 | + ''', expect_fail=True) |
1725 | + self.assertIn('Open vSwitch patch ports must be of different name', err) |
1726 | ++ |
1727 | ++ def test_file_paths_escaped(self): |
1728 | ++ self.generate('''network: |
1729 | ++ version: 2 |
1730 | ++ bridges: |
1731 | ++ "abc/../../123": |
1732 | ++ openvswitch: {} |
1733 | ++ vlans: |
1734 | ++ "abc/../../123.100": |
1735 | ++ id: 100 |
1736 | ++ link: "abc/../../123" |
1737 | ++''') |
1738 | ++ self.assert_ovs({'abc%2F..%2F..%2F123.service': OVS_BR_EMPTY % {'iface': 'abc/../../123'}, |
1739 | ++ 'abc%2F..%2F..%2F123.100.service': OVS_VIRTUAL % {'iface': 'abc/../../123.100', 'extra': |
1740 | ++ '''Requires=netplan-ovs-abc%2F..%2F..%2F123.service |
1741 | ++After=netplan-ovs-abc%2F..%2F..%2F123.service |
1742 | ++ |
1743 | ++[Service] |
1744 | ++Type=oneshot |
1745 | ++TimeoutStartSec=10s |
1746 | ++ExecStart=/usr/bin/ovs-vsctl --may-exist add-br abc/../../123.100 abc/../../123 100 |
1747 | ++ExecStart=/usr/bin/ovs-vsctl set Interface abc/../../123.100 external-ids:netplan=true |
1748 | ++'''}, |
1749 | ++ 'cleanup.service': OVS_CLEANUP % {'iface': 'cleanup'}}) |
1750 | diff --git a/debian/patches/lp2066258/0016-backends-escape-semicolons-in-service-units.patch b/debian/patches/lp2066258/0016-backends-escape-semicolons-in-service-units.patch |
1751 | new file mode 100644 |
1752 | index 0000000..ef7e7af |
1753 | --- /dev/null |
1754 | +++ b/debian/patches/lp2066258/0016-backends-escape-semicolons-in-service-units.patch |
1755 | @@ -0,0 +1,292 @@ |
1756 | +From: Danilo Egea Gondolfo <danilogondolfo@gmail.com> |
1757 | +Date: Fri, 24 May 2024 14:11:12 +0100 |
1758 | +Subject: backends: escape semicolons in service units |
1759 | + |
1760 | +Semicolons separated from other words by a combination of spaces and/or |
1761 | +tabs will be escaped. |
1762 | +--- |
1763 | + src/networkd.c | 7 +++++ |
1764 | + src/openvswitch.c | 3 +++ |
1765 | + src/sriov.c | 3 +++ |
1766 | + src/util-internal.h | 3 +++ |
1767 | + src/util.c | 34 +++++++++++++++++++++++ |
1768 | + tests/ctests/test_netplan_misc.c | 45 +++++++++++++++++++++++++++++++ |
1769 | + tests/generator/test_ovs.py | 58 ++++++++++++++++++++++++++++++++++++++++ |
1770 | + tests/test_sriov.py | 29 ++++++++++++++++++++ |
1771 | + 8 files changed, 182 insertions(+) |
1772 | + |
1773 | +diff --git a/src/networkd.c b/src/networkd.c |
1774 | +index e66ab4c..f9fefe9 100644 |
1775 | +--- a/src/networkd.c |
1776 | ++++ b/src/networkd.c |
1777 | +@@ -301,6 +301,9 @@ write_regdom(const NetplanNetDefinition* def, const char* rootdir, GError** erro |
1778 | + g_string_append(s, "\n[Service]\nType=oneshot\n"); |
1779 | + g_string_append_printf(s, "ExecStart="SBINDIR"/iw reg set %s\n", def->regulatory_domain); |
1780 | + |
1781 | ++ g_autofree char* new_s = _netplan_scrub_systemd_unit_contents(s->str); |
1782 | ++ g_string_free(s, TRUE); |
1783 | ++ s = g_string_new(new_s); |
1784 | + _netplan_g_string_free_to_file_with_permissions(s, rootdir, path, NULL, "root", "root", 0640); |
1785 | + safe_mkdir_p_dir(link); |
1786 | + if (symlink(path, link) < 0 && errno != EEXIST) { |
1787 | +@@ -1191,6 +1194,10 @@ write_wpa_unit(const NetplanNetDefinition* def, const char* rootdir) |
1788 | + } else { |
1789 | + g_string_append(s, " -Dnl80211,wext\n"); |
1790 | + } |
1791 | ++ |
1792 | ++ g_autofree char* new_s = _netplan_scrub_systemd_unit_contents(s->str); |
1793 | ++ g_string_free(s, TRUE); |
1794 | ++ s = g_string_new(new_s); |
1795 | + _netplan_g_string_free_to_file_with_permissions(s, rootdir, path, NULL, "root", "root", 0640); |
1796 | + } |
1797 | + |
1798 | +diff --git a/src/openvswitch.c b/src/openvswitch.c |
1799 | +index b33769d..7d31e7f 100644 |
1800 | +--- a/src/openvswitch.c |
1801 | ++++ b/src/openvswitch.c |
1802 | +@@ -62,6 +62,9 @@ write_ovs_systemd_unit(const char* id, const GString* cmds, const char* rootdir, |
1803 | + g_string_append(s, "\n[Service]\nType=oneshot\nTimeoutStartSec=10s\n"); |
1804 | + g_string_append(s, cmds->str); |
1805 | + |
1806 | ++ g_autofree char* new_s = _netplan_scrub_systemd_unit_contents(s->str); |
1807 | ++ g_string_free(s, TRUE); |
1808 | ++ s = g_string_new(new_s); |
1809 | + _netplan_g_string_free_to_file_with_permissions(s, rootdir, path, NULL, "root", "root", 0640); |
1810 | + |
1811 | + safe_mkdir_p_dir(link); |
1812 | +diff --git a/src/sriov.c b/src/sriov.c |
1813 | +index c3cd80d..710f1a8 100644 |
1814 | +--- a/src/sriov.c |
1815 | ++++ b/src/sriov.c |
1816 | +@@ -53,6 +53,9 @@ write_sriov_rebind_systemd_unit(GHashTable* pfs, const char* rootdir, GError** e |
1817 | + g_string_truncate(interfaces, interfaces->len-1); /* cut trailing whitespace */ |
1818 | + g_string_append_printf(s, "ExecStart=" SBINDIR "/netplan rebind %s\n", interfaces->str); |
1819 | + |
1820 | ++ g_autofree char* new_s = _netplan_scrub_systemd_unit_contents(s->str); |
1821 | ++ g_string_free(s, TRUE); |
1822 | ++ s = g_string_new(new_s); |
1823 | + _netplan_g_string_free_to_file_with_permissions(s, rootdir, path, NULL, "root", "root", 0640); |
1824 | + g_string_free(interfaces, TRUE); |
1825 | + |
1826 | +diff --git a/src/util-internal.h b/src/util-internal.h |
1827 | +index f6f36fc..e9c2d13 100644 |
1828 | +--- a/src/util-internal.h |
1829 | ++++ b/src/util-internal.h |
1830 | +@@ -185,3 +185,6 @@ _netplan_netdef_pertype_iter_free(struct netdef_pertype_iter* it); |
1831 | + |
1832 | + gchar* |
1833 | + _netplan_scrub_string(const char* content); |
1834 | ++ |
1835 | ++gchar* |
1836 | ++_netplan_scrub_systemd_unit_contents(const char* content); |
1837 | +diff --git a/src/util.c b/src/util.c |
1838 | +index a3dae98..30d05cb 100644 |
1839 | +--- a/src/util.c |
1840 | ++++ b/src/util.c |
1841 | +@@ -1235,3 +1235,37 @@ _netplan_scrub_string(const char* content) |
1842 | + |
1843 | + return g_string_free(s, FALSE); |
1844 | + } |
1845 | ++ |
1846 | ++static gboolean |
1847 | ++_is_space_or_tab(char c) |
1848 | ++{ |
1849 | ++ return c == ' ' || c == '\t'; |
1850 | ++} |
1851 | ++ |
1852 | ++char* |
1853 | ++_netplan_scrub_systemd_unit_contents(const char* content) |
1854 | ++{ |
1855 | ++ size_t content_len = strlen(content); |
1856 | ++ // Assume a few replacements will happen to reduce reallocation |
1857 | ++ GString* s = g_string_sized_new(content_len + 8); |
1858 | ++ |
1859 | ++ // Append the first character of "content" to the result string |
1860 | ++ g_string_append_len(s, content, 1); |
1861 | ++ |
1862 | ++ // Walk from the second element to the one before the last looking for isolated semicolons |
1863 | ++ // A semicolon is isolated if it's surrounded by either tabs or spaces |
1864 | ++ const char* p = content + 1; |
1865 | ++ while (p < (content + content_len - 1)) { |
1866 | ++ if (*p == ';' && _is_space_or_tab(*(p - 1)) && _is_space_or_tab(*(p + 1))) { |
1867 | ++ g_string_append_len(s, "\\;", 2); |
1868 | ++ } else { |
1869 | ++ g_string_append_len(s, p, 1); |
1870 | ++ } |
1871 | ++ p++; |
1872 | ++ } |
1873 | ++ |
1874 | ++ // Append the last character of "content" to the result string |
1875 | ++ g_string_append_len(s, p, 1); |
1876 | ++ |
1877 | ++ return g_string_free(s, FALSE); |
1878 | ++} |
1879 | +diff --git a/tests/ctests/test_netplan_misc.c b/tests/ctests/test_netplan_misc.c |
1880 | +index 35bf4f8..aa14a71 100644 |
1881 | +--- a/tests/ctests/test_netplan_misc.c |
1882 | ++++ b/tests/ctests/test_netplan_misc.c |
1883 | +@@ -348,6 +348,50 @@ test_normalize_ip_address(__unused void** state) |
1884 | + assert_string_equal(normalize_ip_address("0.0.0.0/0", AF_INET), "0.0.0.0/0"); |
1885 | + } |
1886 | + |
1887 | ++void |
1888 | ++test_scrub_systemd_unit_content(__unused void** state) |
1889 | ++{ |
1890 | ++ char* str = ";abc;"; |
1891 | ++ char* res = _netplan_scrub_systemd_unit_contents(str); |
1892 | ++ assert_string_equal(res, ";abc;"); |
1893 | ++ g_free(res); |
1894 | ++ |
1895 | ++ str = ";;;;"; |
1896 | ++ res = _netplan_scrub_systemd_unit_contents(str); |
1897 | ++ assert_string_equal(res, ";;;;"); |
1898 | ++ g_free(res); |
1899 | ++ |
1900 | ++ str = " ;;;; "; |
1901 | ++ res = _netplan_scrub_systemd_unit_contents(str); |
1902 | ++ assert_string_equal(res, " ;;;; "); |
1903 | ++ g_free(res); |
1904 | ++ |
1905 | ++ str = "; ; ; ; ;"; |
1906 | ++ res = _netplan_scrub_systemd_unit_contents(str); |
1907 | ++ assert_string_equal(res, "; \\; \\; \\; ;"); |
1908 | ++ g_free(res); |
1909 | ++ |
1910 | ++ str = " ; ; ; ; ; "; |
1911 | ++ res = _netplan_scrub_systemd_unit_contents(str); |
1912 | ++ assert_string_equal(res, " \\; \\; \\; \\; \\; "); |
1913 | ++ g_free(res); |
1914 | ++ |
1915 | ++ str = "a ; ; ; ; ; b"; |
1916 | ++ res = _netplan_scrub_systemd_unit_contents(str); |
1917 | ++ assert_string_equal(res, "a \\; \\; \\; \\; \\; b"); |
1918 | ++ g_free(res); |
1919 | ++ |
1920 | ++ str = "\t;\t; ;\t; \t ;\t "; |
1921 | ++ res = _netplan_scrub_systemd_unit_contents(str); |
1922 | ++ assert_string_equal(res, "\t\\;\t\\; \\;\t\\; \t \\;\t "); |
1923 | ++ g_free(res); |
1924 | ++ |
1925 | ++ str = "\t;\t;\t;\t;\t;\t"; |
1926 | ++ res = _netplan_scrub_systemd_unit_contents(str); |
1927 | ++ assert_string_equal(res, "\t\\;\t\\;\t\\;\t\\;\t\\;\t"); |
1928 | ++ g_free(res); |
1929 | ++} |
1930 | ++ |
1931 | + int |
1932 | + setup(__unused void** state) |
1933 | + { |
1934 | +@@ -380,6 +424,7 @@ main() |
1935 | + cmocka_unit_test(test_util_is_route_rule_present), |
1936 | + cmocka_unit_test(test_util_is_string_in_array), |
1937 | + cmocka_unit_test(test_normalize_ip_address), |
1938 | ++ cmocka_unit_test(test_scrub_systemd_unit_content), |
1939 | + }; |
1940 | + |
1941 | + return cmocka_run_group_tests(tests, setup, tear_down); |
1942 | +diff --git a/tests/generator/test_ovs.py b/tests/generator/test_ovs.py |
1943 | +index e60d497..c40fa47 100644 |
1944 | +--- a/tests/generator/test_ovs.py |
1945 | ++++ b/tests/generator/test_ovs.py |
1946 | +@@ -1128,3 +1128,61 @@ ExecStart=/usr/bin/ovs-vsctl --may-exist add-br abc/../../123.100 abc/../../123 |
1947 | + ExecStart=/usr/bin/ovs-vsctl set Interface abc/../../123.100 external-ids:netplan=true |
1948 | + '''}, |
1949 | + 'cleanup.service': OVS_CLEANUP % {'iface': 'cleanup'}}) |
1950 | ++ |
1951 | ++ def test_control_characters_and_semicolons_escaping(self): |
1952 | ++ self.generate('''network: |
1953 | ++ version: 2 |
1954 | ++ bridges: # bridges first, to trigger multi-pass processing |
1955 | ++ ovs0: |
1956 | ++ interfaces: [eth0, eth1] |
1957 | ++ openvswitch: {} |
1958 | ++ ethernets: |
1959 | ++ eth0: |
1960 | ++ openvswitch: |
1961 | ++ external-ids: |
1962 | ++ "a\\n1\\ra": " ; a ; 1 ;a; ;b\\t;\\t3 ;\\ta\\t; 1" |
1963 | ++ other-config: |
1964 | ++ "a\\n1\\ra": " ; a ; 1 ;a; ;b\\t;\\t3 ;\\ta\\t; 1" |
1965 | ++ dhcp6: true |
1966 | ++ eth1: |
1967 | ++ dhcp4: true |
1968 | ++ openvswitch: |
1969 | ++ other-config: |
1970 | ++ disable-in-band: false\n''', skip_generated_yaml_validation=True) |
1971 | ++ self.assert_ovs({'ovs0.service': OVS_VIRTUAL % {'iface': 'ovs0', 'extra': ''' |
1972 | ++[Service] |
1973 | ++Type=oneshot |
1974 | ++TimeoutStartSec=10s |
1975 | ++ExecStart=/usr/bin/ovs-vsctl --may-exist add-br ovs0 |
1976 | ++ExecStart=/usr/bin/ovs-vsctl --may-exist add-port ovs0 eth1 |
1977 | ++ExecStart=/usr/bin/ovs-vsctl --may-exist add-port ovs0 eth0 |
1978 | ++''' + OVS_BR_DEFAULT % {'iface': 'ovs0'}}, |
1979 | ++ 'eth0.service': OVS_PHYSICAL % {'iface': 'eth0', 'extra': '''\ |
1980 | ++Requires=netplan-ovs-ovs0.service |
1981 | ++After=netplan-ovs-ovs0.service |
1982 | ++ |
1983 | ++[Service] |
1984 | ++Type=oneshot |
1985 | ++TimeoutStartSec=10s |
1986 | ++ExecStart=/usr/bin/ovs-vsctl set Interface eth0 external-ids:a\\n1\\ra= \\; a \\; 1 ;a; ;b\\t;\\t3 ;\\ta\\t; 1 |
1987 | ++ExecStart=/usr/bin/ovs-vsctl set Interface eth0 external-ids:netplan/external-ids/a\\n1\\ra=,;,a,;,1,;a;,;b\\t;\\t3,;\\ta\\t;,1 |
1988 | ++ExecStart=/usr/bin/ovs-vsctl set Interface eth0 other-config:a\\n1\\ra= \\; a \\; 1 ;a; ;b\\t;\\t3 ;\\ta\\t; 1 |
1989 | ++ExecStart=/usr/bin/ovs-vsctl set Interface eth0 external-ids:netplan/other-config/a\\n1\\ra=,;,a,;,1,;a;,;b\\t;\\t3,;\\ta\\t;,1 |
1990 | ++'''}, |
1991 | ++ 'eth1.service': OVS_PHYSICAL % {'iface': 'eth1', 'extra': '''\ |
1992 | ++Requires=netplan-ovs-ovs0.service |
1993 | ++After=netplan-ovs-ovs0.service |
1994 | ++ |
1995 | ++[Service] |
1996 | ++Type=oneshot |
1997 | ++TimeoutStartSec=10s |
1998 | ++ExecStart=/usr/bin/ovs-vsctl set Interface eth1 other-config:disable-in-band=false |
1999 | ++ExecStart=/usr/bin/ovs-vsctl set Interface eth1 external-ids:netplan/other-config/disable-in-band=false |
2000 | ++'''}, |
2001 | ++ 'cleanup.service': OVS_CLEANUP % {'iface': 'cleanup'}}) |
2002 | ++ # Confirm that the networkd config is still sane |
2003 | ++ self.assert_networkd({'ovs0.network': ND_EMPTY % ('ovs0', 'ipv6'), |
2004 | ++ 'eth0.network': (ND_DHCP6 % 'eth0') |
2005 | ++ .replace('LinkLocalAddressing=ipv6', 'LinkLocalAddressing=no\nBridge=ovs0'), |
2006 | ++ 'eth1.network': (ND_DHCP4 % 'eth1') |
2007 | ++ .replace('LinkLocalAddressing=ipv6', 'LinkLocalAddressing=no\nBridge=ovs0')}) |
2008 | +diff --git a/tests/test_sriov.py b/tests/test_sriov.py |
2009 | +index 3c06e43..054500c 100644 |
2010 | +--- a/tests/test_sriov.py |
2011 | ++++ b/tests/test_sriov.py |
2012 | +@@ -913,6 +913,35 @@ After=sys-subsystem-net-devices-engreen.device |
2013 | + [Service] |
2014 | + Type=oneshot |
2015 | + ExecStart=/usr/sbin/netplan rebind enblue engreen |
2016 | ++'''}) |
2017 | ++ |
2018 | ++ def test_escaping_semicolons_from_unit_file(self): |
2019 | ++ ''' Check if semicolons and line breaks are properly escaped in the generated |
2020 | ++ systemd service unit. |
2021 | ++ ''' |
2022 | ++ self.generate('''network: |
2023 | ++ version: 2 |
2024 | ++ ethernets: |
2025 | ++ engreen: |
2026 | ++ embedded-switch-mode: switchdev |
2027 | ++ delay-virtual-functions-rebind: true |
2028 | ++ enblue: |
2029 | ++ match: {driver: dummy_driver} |
2030 | ++ set-name: ";en ; a\\t;\\tb ;\\tc\\t; d; \\n;\\nabc" |
2031 | ++ embedded-switch-mode: legacy |
2032 | ++ delay-virtual-functions-rebind: true |
2033 | ++ virtual-function-count: 4 |
2034 | ++ sriov_vf0: |
2035 | ++ link: engreen''', skip_generated_yaml_validation=True) |
2036 | ++ self.assert_sriov({'rebind.service': '''[Unit] |
2037 | ++Description=(Re-)bind SR-IOV Virtual Functions to their driver |
2038 | ++After=network.target |
2039 | ++After=sys-subsystem-net-devices-;en \\; a\\t;\\tb ;\\tc\\t; d; \\n;\\nabc.device |
2040 | ++After=sys-subsystem-net-devices-engreen.device |
2041 | ++ |
2042 | ++[Service] |
2043 | ++Type=oneshot |
2044 | ++ExecStart=/usr/sbin/netplan rebind ;en \\; a\\t;\\tb ;\\tc\\t; d; \\n;\\nabc engreen |
2045 | + '''}) |
2046 | + |
2047 | + def test_rebind_not_delayed(self): |
2048 | diff --git a/debian/patches/series b/debian/patches/series |
2049 | index 8021670..f09ed4e 100644 |
2050 | --- a/debian/patches/series |
2051 | +++ b/debian/patches/series |
2052 | @@ -4,3 +4,8 @@ lp2041727/0005-Update-ovs.py-to-check-if-ovsdb-server.service-is-in.patch |
2053 | 0007-tests-assert-generated-.service-files-in-assert_srio.patch |
2054 | 0008-tests-sriov-test-if-the-generated-netplan-rebind-ser.patch |
2055 | 0009-sriov-don-t-generate-duplicate-entries-in-the-rebind.patch |
2056 | +lp2065738/0012-cli-generate-call-daemon-reload-after-generate.patch |
2057 | +lp2065738/0013-libnetplan-use-more-restrictive-file-permissions.patch |
2058 | +lp2066258/0014-libnetplan-escape-control-characters.patch |
2059 | +lp2066258/0015-backends-escape-file-paths.patch |
2060 | +lp2066258/0016-backends-escape-semicolons-in-service-units.patch |
LGTM. The new security patches match the noble patches (ignoring patch fuzz).
I've just applied some tiny fixes to the version number (removing ~ppa suffix) and update d/changelog to not drop the previous 0.107-5ubuntu0.3 and 0.107-5ubuntu0.4 updates.