From e3b946e34b109406f38bfa5f78eb9a2ec7502bc9 Mon Sep 17 00:00:00 2001 From: Oleksandr Kuchmystyi Date: Mon, 15 Jun 2026 17:00:56 +0300 Subject: password-reset: T8985: Fix unbounded `sed` ranges corrupting other user blocks `sed` ranges keyed on a field name (`plaintext-password`, `encrypted-password`, `authentication {`) are not bounded to the target user's block. When the field is absent the range stays open past the user's closing brace and matches the first occurrence of that field in a later account. --- src/system/standalone_root_pw_reset | 42 +++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) (limited to 'src/system') diff --git a/src/system/standalone_root_pw_reset b/src/system/standalone_root_pw_reset index aa712ad52..4cd4b98d4 100755 --- a/src/system/standalone_root_pw_reset +++ b/src/system/standalone_root_pw_reset @@ -27,7 +27,11 @@ CF=/opt/vyatta/etc/config/config.boot ADMIN=vyos set_encrypted_password() { - local user=$1 epwd=$2 file=$3 + local user_raw=$1 epwd=$2 file=$3 + local user + + # Escape the username before using it in awk/sed regexes + user=$(printf '%s' "$user_raw" | sed -e 's/\./\\&/g') # Check if encrypted-password exists within this specific user's block if awk "/ user $user \{/{f=1} f && /encrypted-password/{found=1; exit} f && /^ }/{exit} END{exit !found}" "$file"; then @@ -35,15 +39,41 @@ set_encrypted_password() { sed -i \ -e "/ user $user {/,/encrypted-password/s/encrypted-password .*\$/encrypted-password \"$epwd\"/" "$file" else - # Insert encrypted-password after plaintext-password - sed -i \ - -e "/ user $user {/,/plaintext-password/s/\(plaintext-password .*\)\$/\1\n encrypted-password \"$epwd\"/" "$file" + # Key-only account: no encrypted-password or plaintext-password line exists + # in this user's block (e.g. authentication via SSH key only). + if awk "/ user $user \{/{f=1} f && /authentication \{/{found=1; exit} f && /^ \}/{exit} END{exit !found}" "$file"; then + # 'authentication' block exists - insert encrypted-password inside it + sed -i \ + -e "/ user $user {/,/authentication {/s/\(authentication {\)\$/\1\n encrypted-password \"$epwd\"/" "$file" + else + # No authentication block at all. Without this branch the sed range + # above would overrun the block boundary and corrupt a later account's + # authentication block. Build the section from scratch instead. + sed -i \ + -e "/ user $user {/a\\ + authentication {\\ + encrypted-password \"$epwd\"\\ + plaintext-password \"\"\\ + }" "$file" + fi fi } clear_plaintext_password() { - sed -i \ - -e "/ user $1 {/,/plaintext-password/s/plaintext-password .*\$/plaintext-password \"\"/" $2 + local user_raw=$1 file=$2 + local user + + # Escape the username before using it in awk/sed regexes + user=$(printf '%s' "$user_raw" | sed -e 's/\./\\&/g') + + # Only clear if plaintext-password exists within this specific user's block. + # Without this guard, if the user has no plaintext-password line the sed + # range never closes inside their block and spills into the next account + # that does have one, wiping that account's plaintext-password instead. + if awk "/ user $user \{/{f=1} f && /plaintext-password/{found=1; exit} f && /^ \}/{exit} END{exit !found}" "$file"; then + sed -i \ + -e "/ user $user {/,/plaintext-password/s/plaintext-password .*\$/plaintext-password \"\"/" "$file" + fi } -- cgit v1.2.3