Skip to content

Commit

Permalink
fix(network-manager): ensure safe content of /tmp/dhclient."$ifname".…
Browse files Browse the repository at this point in the history
…dhcpopts

NetworkManager leaves state files behind in "/run/NetworkManager/devices".
These files are in keyfile format (glib's GKeyFile API [1]).

From the statefile, the dracut module writes a .dhcpopts file. And other users
want to parse that file, for example anaconda ([2]). To be fair,
anaconda seems to parse a different file, so I am a bit confused who
uses this file how. In any case, it seems somebody might be tempted to
execute this as a script.

We need to write the .dhcpopts file in a format that is defined and easy
to handle from a shell script. As already previously, this format is
a bash script that sets certain variables. That means, to load the file,
the user could execute it as bash script. But this is dangerous, as the
file contains potentially untrusted data from the network.
Optimally, users still don't trust the .dhcpopts file to be safe for
executing! It would be better if users too try to parse the file
instead of executing it. That is not trivial however because in face
of special characters, as we use bash's `printf '%q'` to escape the values
and parsing bash escaping is not trivial.

Anyway, make sure we properly quote and handle the content so that also
executing is safe. In the best case, there are no special characters
that require escaping, and naive parsing can be done with `sed`.
Otherwise, executing is now also supposed to be safe.

In this case we parse DHCP options from the state file. They are themselves
backslash escaped UTF-8 strings (C escape sequences), which then are stored
via keyfile API. The properly parse them, we would first need to load the file
with GKeyFile (which undoes one level of backslash escaping) and then
use g_str_compress() (to undo the second level). We mimic that with
shell.

[1] https://github.com/rhinstaller/anaconda/blob/b3411d6780aa0d76ee1e81a38710ec05a2d1978b/dracut/fetch-kickstart-net.sh#L30
[2] https://developer.gnome.org/glib/stable/glib-Key-value-file-parser.html

Signed-off-by: Thomas Haller <[email protected]>
  • Loading branch information
thom311 authored and johannbg committed Aug 7, 2021
1 parent c868feb commit e509c63
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 6 deletions.
2 changes: 1 addition & 1 deletion modules.d/35network-manager/module-setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ check() {

# called by dracut
depends() {
echo dbus
echo dbus bash
return 0
}

Expand Down
44 changes: 39 additions & 5 deletions modules.d/35network-manager/nm-run.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/sh
#!/bin/bash

type source_hook > /dev/null 2>&1 || . /lib/dracut-lib.sh

Expand All @@ -24,11 +24,45 @@ if [ -s /run/NetworkManager/initrd/hostname ]; then
cat /run/NetworkManager/initrd/hostname > /proc/sys/kernel/hostname
fi

kf_get_string() {
# NetworkManager writes keyfiles (glib's GKeyFile API). Have a naive
# parser for it.
#
# But GKeyFile will backslash escape certain keys (\s, \t, \n) but also
# escape backslash. As an approximation, interpret the string with printf's
# '%b'.
#
# This is supposed to mimic g_key_file_get_string() (poorly).

v1="$(sed -n "s/^$1=/=/p" | sed '1!d')"
test "$v1" = "${v1#=}" && return 1
printf "%b" "${v1#=}"
}

kf_unescape() {
# Another layer of unescaping. While values in GKeyFile format
# are backslash escaped, the original strings (which are in no
# defined encoding) are backslash escaped too to be valid UTF-8.
# This will undo the second layer of escaping to give binary "strings".
printf "%b" "$1"
}

kf_parse() {
v3="$(kf_get_string "$1")" || return 1
v3="$(kf_unescape "$v3")"
printf '%s=%s\n' "$2" "$(printf '%q' "$v3")"
}

dhcpopts_create() {
kf_parse root-path new_root_path < "$1"
kf_parse next-server new_next_server < "$1"
}

for _i in /sys/class/net/*; do
state=/run/NetworkManager/devices/$(cat "$_i"/ifindex)
grep -q connection-uuid= "$state" 2> /dev/null || continue
ifname=${_i##*/}
sed -n 's/root-path/new_root_path/p;s/next-server/new_next_server/p' < "$state" > /tmp/dhclient."$ifname".dhcpopts
state="/run/NetworkManager/devices/$(cat "$_i"/ifindex)"
grep -q '^connection-uuid=' "$state" 2> /dev/null || continue
ifname="${_i##*/}"
dhcpopts_create "$state" > /tmp/dhclient."$ifname".dhcpopts
source_hook initqueue/online "$ifname"
/sbin/netroot "$ifname"
done
Expand Down

0 comments on commit e509c63

Please sign in to comment.