From nobody Tue May 12 07:53:00 2026 X-Original-To: dev-commits-src-all@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4gF8353WfQz6dDvw for ; Tue, 12 May 2026 07:53:05 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R13" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4gF8352Bstz42hD for ; Tue, 12 May 2026 07:53:05 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1778572385; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=y2tfkr/3GtdCzqR4dVvq9Y7Y+9vuttD4v4cliyY+5sI=; b=loq8E4yZUcDg+VuKwhhQYg9P3LagwnxAtDz+eSVJEl/D2tyZJtltIERJldhR4eP0v7d/9V myJ8CfBoSx6CkQ8/Rl49pzRHrD/anUkCeUgL/CePy9AFm8N7qEZnUgy41ficXWyS+vpjY2 mHrKjYPm3xmu/rjMnvHUuRiDj4M+tLO1YxpQna+oZiy5UXGsS8SZhPw56EChLA18jbbLU7 lBQMzVmtm7kpYA6XWjSRkoTWsqcLrrd1Pi1GNHgi3bVlSOI5K837G6MhenBXs38+TFdd48 b3UxE6DxUtmq4CtteU931K0UwCYQj6zfcUJepU+14dcidnbhxqMdIBcYdW3y0A== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1778572385; a=rsa-sha256; cv=none; b=KZyuJ4O0hy6C2NPRuf5iIXl6qD1rKmWcU5lfUlQSnDvX1SXRtFWaK8AZSZyieyrXEBCpdC qwmU+hmmgaQzApPDrR1uqx2jgDazdrwEKUirrysnHzX5rPsjzJQtwftbumeblgK0LvjCF1 hHhj5qkiSpPf9f9xBVJXe8BX6saqKoF6Y0Il2tCJkXv2Rf/z7SUFvRoHKojaGjq0xEMWgu 1o8PtbKRNnCyum/uLpnbkQkgqMKp4swj46u1u2oHeUYPdPbA8cLKcs0hHKCwxMTvmE6fDh thw2dkFeWpkIeMRJUsmS3CTO2x5OmnpxmM2D9SJran6MPe0APsdWSecICGVgeQ== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1778572385; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=y2tfkr/3GtdCzqR4dVvq9Y7Y+9vuttD4v4cliyY+5sI=; b=E+LdPVKIR1zTMhLq6fI0l9iaHtRD29w7xOG/ycxISf6YeWOMQL1enruIYO0EYCkfmLiAQI aT1rSTzjSWr5RMynFF8ROB66/kPQtiadrdM2QUmc+oztmvJrc5nMceWMZavAEylO20BT+Z OiAJ8ShuCfGYejSXjTxvds/oyGqV0QSYiCUvPPLdJqlls8/e8AFuO7GXkBL8KoWQrAzJrG Y95bRs4LV1tMDx2DC5GQXiof/SqnDIIbNVBpaHP7c9dD0XMNESUMIXvMEhbw5lKUMkhCGJ B6lEyXLVP3WnpBK+uAe2dD8+fEmsOAcreYk1wQEP9jAcL4uMe5JRGIH7dCn9gg== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4gF8351klxzvPX for ; Tue, 12 May 2026 07:53:05 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 24b1f by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Tue, 12 May 2026 07:53:00 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Baptiste Daroussin Subject: git: 8b70a203be10 - main - nuageinit: fix command injection and related issues List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-all@freebsd.org Sender: owner-dev-commits-src-all@FreeBSD.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: bapt X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 8b70a203be10411c560ed303ab25713d70b316e9 Auto-Submitted: auto-generated Date: Tue, 12 May 2026 07:53:00 +0000 Message-Id: <6a02dc5c.24b1f.734d8e95@gitrepo.freebsd.org> The branch main has been updated by bapt: URL: https://cgit.FreeBSD.org/src/commit/?id=8b70a203be10411c560ed303ab25713d70b316e9 commit 8b70a203be10411c560ed303ab25713d70b316e9 Author: Baptiste Daroussin AuthorDate: 2026-05-07 18:22:14 +0000 Commit: Baptiste Daroussin CommitDate: 2026-05-12 07:52:32 +0000 nuageinit: fix command injection and related issues - Add shell_escape() helper to safely escape shell arguments - Apply shell_escape to all user-controlled values in shell commands: adduser (usershow, useradd, lock, primary_group, groups) addgroup (groupshow, groupadd, members) exec_change_password (usermod) settimezone (tzsetup root and timezone) install_package (pkg package names) - Escape double quotes in hostname when writing rc.conf.d/hostname - Add missing 'local' declaration for resolvconf_command in nameservers() - Escape interface name in resolvconf -a command - Change open_resolvconf_conf() from 'w' to 'a' mode to prevent data loss when nameservers() is called multiple times - Clean up stale resolvconf.conf at the start of each boot (skip on postnet to preserve config written by first call) MFC After: 1 day --- libexec/nuageinit/nuage.lua | 43 +++++++++++++++++++++++------------- libexec/nuageinit/nuageinit | 17 ++++++++++++-- libexec/nuageinit/tests/nuageinit.sh | 6 ++--- 3 files changed, 46 insertions(+), 20 deletions(-) diff --git a/libexec/nuageinit/nuage.lua b/libexec/nuageinit/nuage.lua index 2d962b540b23..f3c23a7c3eb8 100644 --- a/libexec/nuageinit/nuage.lua +++ b/libexec/nuageinit/nuage.lua @@ -52,6 +52,10 @@ local function decode_base64(input) return table.concat(result) end +local function shell_escape(s) + return "'" .. string.gsub(s, "'", "'\\''") .. "'" +end + local function warnmsg(str, prepend) if not str then return @@ -121,7 +125,7 @@ local function sethostname(hostname) warnmsg("Impossible to open " .. hostnamepath .. ":" .. err) return end - f:write('hostname="' .. hostname .. '"\n') + f:write('hostname="' .. hostname:gsub('"', '\\"') .. '"\n') f:close() end @@ -199,7 +203,7 @@ local function adduser(pwd) if root then cmd = cmd .. "-R " .. root .. " " end - local f = io.popen(cmd .. " usershow " .. pwd.name .. " -7 2> /dev/null") + local f = io.popen(cmd .. " usershow " .. shell_escape(pwd.name) .. " -7 2> /dev/null") local pwdstr = f:read("*a") f:close() if pwdstr:len() ~= 0 then @@ -220,13 +224,17 @@ local function adduser(pwd) -- a warning but creates the user anyway. list = purge_group(list) if #list > 0 then - extraargs = " -G " .. table.concat(list, ",") + local escaped_list = {} + for _, g in ipairs(list) do + table.insert(escaped_list, shell_escape(g)) + end + extraargs = " -G " .. table.concat(escaped_list, ",") end end -- pw will automatically create a group named after the username -- do not add a -g option in this case if pwd.primary_group and pwd.primary_group ~= pwd.name then - extraargs = extraargs .. " -g " .. pwd.primary_group + extraargs = extraargs .. " -g " .. shell_escape(pwd.primary_group) end if not pwd.no_create_home then extraargs = extraargs .. " -m " @@ -248,9 +256,9 @@ local function adduser(pwd) if root then cmd = cmd .. "-R " .. root .. " " end - cmd = cmd .. "useradd -n " .. pwd.name .. " -M 0755 -w none " - cmd = cmd .. extraargs .. " -c '" .. pwd.gecos - cmd = cmd .. "' -d '" .. pwd.homedir .. "' -s " .. pwd.shell .. postcmd + cmd = cmd .. "useradd -n " .. shell_escape(pwd.name) .. " -M 0755 -w none " + cmd = cmd .. extraargs .. " -c " .. shell_escape(pwd.gecos) + cmd = cmd .. " -d " .. shell_escape(pwd.homedir) .. " -s " .. shell_escape(pwd.shell) .. postcmd f = io.popen(cmd, "w") if input then @@ -267,7 +275,7 @@ local function adduser(pwd) if root then cmd = cmd .. "-R " .. root .. " " end - cmd = cmd .. "lock " .. pwd.name + cmd = cmd .. "lock " .. shell_escape(pwd.name) os.execute(cmd) end return pwd.homedir @@ -283,7 +291,7 @@ local function addgroup(grp) if root then cmd = cmd .. "-R " .. root .. " " end - local f = io.popen(cmd .. " groupshow " .. grp.name .. " 2> /dev/null") + local f = io.popen(cmd .. " groupshow " .. shell_escape(grp.name) .. " 2> /dev/null") local grpstr = f:read("*a") f:close() if grpstr:len() ~= 0 then @@ -292,13 +300,17 @@ local function addgroup(grp) local extraargs = "" if grp.members then local list = splitlist(grp.members) - extraargs = " -M " .. table.concat(list, ",") + local escaped_list = {} + for _, m in ipairs(list) do + table.insert(escaped_list, shell_escape(m)) + end + extraargs = " -M " .. table.concat(escaped_list, ",") end cmd = "pw " if root then cmd = cmd .. "-R " .. root .. " " end - cmd = cmd .. "groupadd -n " .. grp.name .. extraargs + cmd = cmd .. "groupadd -n " .. shell_escape(grp.name) .. extraargs local r = os.execute(cmd) if not r then warnmsg("fail to add group " .. grp.name) @@ -484,7 +496,7 @@ local function exec_change_password(user, password, type, expire) postcmd = " -w random" end end - cmd = cmd .. "usermod " .. user .. postcmd + cmd = cmd .. "usermod " .. shell_escape(user) .. postcmd if expire then cmd = cmd .. " -p 1" else @@ -577,7 +589,7 @@ local function settimezone(timezone) root = "/" end - local f, _, rc = os.execute("tzsetup -s -C " .. root .. " " .. timezone) + local f, _, rc = os.execute("tzsetup -s -C " .. shell_escape(root) .. " " .. shell_escape(timezone)) if not f then warnmsg("Impossible to configure time zone ( rc = " .. rc .. " )") @@ -600,8 +612,8 @@ local function install_package(package) if package == nil then return true end - local install_cmd = "pkg install -y " .. package - local test_cmd = "pkg info -q " .. package + local install_cmd = "pkg install -y " .. shell_escape(package) + local test_cmd = "pkg info -q " .. shell_escape(package) if os.getenv("NUAGE_RUN_TESTS") then print(install_cmd) print(test_cmd) @@ -683,6 +695,7 @@ local function addfile(file, defer) end local n = { + shell_escape = shell_escape, warn = warnmsg, err = errmsg, chmod = chmod, diff --git a/libexec/nuageinit/nuageinit b/libexec/nuageinit/nuageinit index a1ebd3f52b25..fc8d9582b9c6 100755 --- a/libexec/nuageinit/nuageinit +++ b/libexec/nuageinit/nuageinit @@ -67,7 +67,14 @@ local function open_resolv_conf() end local function open_resolvconf_conf() - return openat("/etc", "resolvconf.conf") + local path_dir = root .. "/etc" + local path_name = path_dir .. "/resolvconf.conf" + nuage.mkdir_p(path_dir) + local f, err = io.open(path_name, "a") + if not f then + nuage.err("unable to open " .. path_name .. ": " .. err) + end + return f, path_name end local function get_ifaces_by_mac() @@ -271,8 +278,9 @@ local function nameservers(interface, obj) end -- Only call resolvconf with interface if interface is provided + local resolvconf_command if interface then - resolvconf_command = "resolvconf -a " .. interface .. " < " .. resolv_conf + resolvconf_command = "resolvconf -a " .. nuage.shell_escape(interface) .. " < " .. resolv_conf else resolvconf_command = "resolvconf -u" end @@ -738,6 +746,11 @@ local function load_userdata() return line, obj end +-- Clean up stale resolvconf.conf from previous boot +if citype ~= "postnet" then + os.remove(root .. "/etc/resolvconf.conf") +end + if citype == "config-2" then -- network config2_network(ni_path) diff --git a/libexec/nuageinit/tests/nuageinit.sh b/libexec/nuageinit/tests/nuageinit.sh index 1fd68d5a178e..89207fdf0aca 100644 --- a/libexec/nuageinit/tests/nuageinit.sh +++ b/libexec/nuageinit/tests/nuageinit.sh @@ -801,7 +801,7 @@ packages: - yeah/plop EOF chmod 755 "${PWD}"/media/nuageinit/user_data - atf_check -s exit:0 -o inline:"pkg install -y yeah/plop\npkg info -q yeah/plop\n" /usr/libexec/nuageinit "${PWD}"/media/nuageinit postnet + atf_check -s exit:0 -o inline:"pkg install -y 'yeah/plop'\npkg info -q 'yeah/plop'\n" /usr/libexec/nuageinit "${PWD}"/media/nuageinit postnet cat > media/nuageinit/user_data << 'EOF' #cloud-config @@ -809,7 +809,7 @@ packages: - curl EOF chmod 755 "${PWD}"/media/nuageinit/user_data - atf_check -o inline:"pkg install -y curl\npkg info -q curl\n" /usr/libexec/nuageinit "${PWD}"/media/nuageinit postnet + atf_check -o inline:"pkg install -y 'curl'\npkg info -q 'curl'\n" /usr/libexec/nuageinit "${PWD}"/media/nuageinit postnet cat > media/nuageinit/user_data << 'EOF' #cloud-config @@ -818,7 +818,7 @@ packages: - meh: bla EOF chmod 755 "${PWD}"/media/nuageinit/user_data - atf_check -o inline:"pkg install -y curl\npkg info -q curl\n" -e inline:"nuageinit: Invalid type: table for packages entry number 2\n" /usr/libexec/nuageinit "${PWD}"/media/nuageinit postnet + atf_check -o inline:"pkg install -y 'curl'\npkg info -q 'curl'\n" -e inline:"nuageinit: Invalid type: table for packages entry number 2\n" /usr/libexec/nuageinit "${PWD}"/media/nuageinit postnet } config2_userdata_update_packages_body()