git: 46d1758aa7a2 - main - nuageinit: add hostname validation (RFC 952/1123) to sethostname()

From: Baptiste Daroussin <bapt_at_FreeBSD.org>
Date: Thu, 04 Jun 2026 21:15:43 UTC
The branch main has been updated by bapt:

URL: https://cgit.FreeBSD.org/src/commit/?id=46d1758aa7a2af37a356a93812b492a406c6ffd4

commit 46d1758aa7a2af37a356a93812b492a406c6ffd4
Author:     Baptiste Daroussin <bapt@FreeBSD.org>
AuthorDate: 2026-06-04 18:26:49 +0000
Commit:     Baptiste Daroussin <bapt@FreeBSD.org>
CommitDate: 2026-06-04 18:26:49 +0000

    nuageinit: add hostname validation (RFC 952/1123) to sethostname()
    
    Validate hostnames before writing them:
    - Reject empty hostnames
    - Reject hostnames longer than 253 characters
    - Reject hostnames with invalid characters
    - Reject hostnames starting or ending with dot/hyphen
    - Reject labels longer than 63 characters
    - Reject labels starting or ending with hyphen
    
    Expand the sethostname test to cover all rejection cases.
    Update nuage.sh sethostname_body to ignore stderr (warnings).
---
 libexec/nuageinit/nuage.lua             | 27 +++++++++++++
 libexec/nuageinit/tests/nuage.sh        |  2 +-
 libexec/nuageinit/tests/sethostname.lua | 68 +++++++++++++++++++++++++++++++++
 3 files changed, 96 insertions(+), 1 deletion(-)

diff --git a/libexec/nuageinit/nuage.lua b/libexec/nuageinit/nuage.lua
index 4f25e79ccefc..a491ca8d9df6 100644
--- a/libexec/nuageinit/nuage.lua
+++ b/libexec/nuageinit/nuage.lua
@@ -119,6 +119,33 @@ local function sethostname(hostname)
 	if hostname == nil then
 		return
 	end
+	-- Basic hostname validation (RFC 952/1123)
+	if #hostname == 0 then
+		warnmsg("hostname is empty, ignoring")
+		return
+	end
+	if #hostname > 253 then
+		warnmsg("hostname too long (" .. #hostname .. " > 253), ignoring")
+		return
+	end
+	if hostname:match("[^a-zA-Z0-9%.%-]") then
+		warnmsg("hostname contains invalid characters: " .. hostname)
+		return
+	end
+	if hostname:match("^[%.%-]") or hostname:match("[%.%-]$") then
+		warnmsg("hostname must not start or end with a dot or hyphen: " .. hostname)
+		return
+	end
+	for label in hostname:gmatch("[^.]+") do
+		if #label > 63 then
+			warnmsg("hostname label too long (" .. #label .. " > 63): " .. label)
+			return
+		end
+		if label:match("^-") or label:match("-$") then
+			warnmsg("hostname label starts or ends with hyphen: " .. label)
+			return
+		end
+	end
 	local root = os.getenv("NUAGE_FAKE_ROOTDIR")
 	if not root then
 		root = ""
diff --git a/libexec/nuageinit/tests/nuage.sh b/libexec/nuageinit/tests/nuage.sh
index 348a8d93ba09..97c5224c7813 100644
--- a/libexec/nuageinit/tests/nuage.sh
+++ b/libexec/nuageinit/tests/nuage.sh
@@ -29,7 +29,7 @@ settimezone_body()
 
 sethostname_body()
 {
-	atf_check /usr/libexec/flua $(atf_get_srcdir)/sethostname.lua
+	atf_check -e ignore /usr/libexec/flua $(atf_get_srcdir)/sethostname.lua
 	if [ ! -f etc/rc.conf.d/hostname ]; then
 		atf_fail "hostname not written"
 	fi
diff --git a/libexec/nuageinit/tests/sethostname.lua b/libexec/nuageinit/tests/sethostname.lua
index 47632497b545..0bc7eb2c4475 100644
--- a/libexec/nuageinit/tests/sethostname.lua
+++ b/libexec/nuageinit/tests/sethostname.lua
@@ -1,5 +1,73 @@
 #!/usr/libexec/flua
+---
+-- SPDX-License-Identifier: BSD-2-Clause
+--
+-- Copyright (c) 2026 Baptiste Daroussin <bapt@FreeBSD.org>
 
 local n = require("nuage")
 
+local root = os.getenv("NUAGE_FAKE_ROOTDIR")
+if not root then
+	root = ""
+end
+
+local hostnamepath = root .. "/etc/rc.conf.d/hostname"
+
+local function check_hostname(expected)
+	local f = io.open(hostnamepath, "r")
+	if not f then
+		n.err("hostname file not found, expected: " .. expected)
+	end
+	local content = f:read("*a")
+	f:close()
+	local expected_content = 'hostname="' .. expected:gsub('"', '\\"') .. '"\n'
+	if content ~= expected_content then
+		n.err("hostname mismatch: got '" .. content ..
+		    "', expected '" .. expected_content .. "'")
+	end
+end
+
+local function check_no_hostname()
+	if io.open(hostnamepath, "r") then
+		n.err("hostname file should not exist")
+	end
+end
+
+-- nil hostname: no-op
+n.sethostname(nil)
+check_no_hostname()
+
+-- Empty hostname: invalid
+n.sethostname("")
+check_no_hostname()
+
+-- Hostname too long (>253 chars): invalid
+n.sethostname(string.rep("a", 254))
+check_no_hostname()
+
+-- Invalid characters: invalid
+n.sethostname("host;name")
+check_no_hostname()
+
+-- Starts with dot: invalid
+n.sethostname(".hostname")
+check_no_hostname()
+
+-- Ends with hyphen: invalid
+n.sethostname("hostname-")
+check_no_hostname()
+
+-- Label too long (>63 chars): invalid
+n.sethostname(string.rep("a", 64) .. ".example.com")
+check_no_hostname()
+
+-- Label starts with hyphen: invalid
+n.sethostname("myhost.-label.com")
+check_no_hostname()
+
+-- Valid simple hostname
+n.sethostname("myhostname")
+check_hostname("myhostname")
+
+-- Final: set a valid hostname for the shell test
 n.sethostname("myhostname")