Skip to content

net/freeradius: quote password values literally so % is not expanded - #5695

Open
dotCooCoo wants to merge 3 commits into
opnsense:masterfrom
dotCooCoo:fix/freeradius-user-password-quoting
Open

net/freeradius: quote password values literally so % is not expanded#5695
dotCooCoo wants to merge 3 commits into
opnsense:masterfrom
dotCooCoo:fix/freeradius-user-password-quoting

Conversation

@dotCooCoo

@dotCooCoo dotCooCoo commented Sep 2, 2026

Copy link
Copy Markdown

Important notices

Before you submit a pull request, we ask you kindly to acknowledge the following:


Describe the problem

User.xml permits % in the password field, and the users template renders the value
inside double quotes. FreeRADIUS applies dynamic expansion to double-quoted strings, so it
reads % as the start of an expansion sequence.

Which failure you get depends on the character after the %. A valid expansion such as %c,
%d or %e substitutes successfully, so the stored password silently becomes a different
string and PAP reports Cleartext password does not match "known good" password, with the
generated file still showing the text that was typed. #5678 documents that mode with a
character-by-character table. A sequence that is not a valid expansion, such as %7, fails to
parse instead, rlm_files skips the whole user entry, and the server rejects the user for
having no Auth-Type. That rejection is the same one a wrong password produces.

The template also renders both Tunnel-Password values with no quoting at all, at lines 28
and 124. Those masks permit % as well, so the same expansion applies, and an unquoted value
additionally ends at the first space. A tunnel password of two words reaches FreeRADIUS as
two.

The double-quoted password rendering dates to the plugin's first commit in 2017.


Describe the proposed solution

service/templates/OPNsense/Freeradius/users gains a q() macro that wraps a value in single
quotes and escapes backslash and single quote, and the interpolated values go through it.
FreeRADIUS does not expand single-quoted strings, so % and ${...} reach the server
unchanged, and backslash and single quote are the only two escape sequences FreeRADIUS
processes inside them. The same plugin already single-quotes values elsewhere, in
mods-enabled-ldap at lines 15 and 25.

The six call sites are the password, Max-Daily-Session, Simultaneous-Use, Login-Time,
and both Tunnel-Password attributes. Only the password and the two tunnel passwords can
currently hold a %. The other three go through the same macro so the file has one rule,
rather than a per-field judgment about which masks happen to be safe.

models/OPNsense/Freeradius/User.xml and models/OPNsense/Freeradius/General.xml widen the
password, tunnel_password and fallback_tunnel_password masks to printable ASCII, keeping
the existing 128 character limit on each. forms/dialogEditFreeRADIUSUser.xml and
forms/general.xml bring the three help texts in line with the masks; the password help text
also listed a comma that its mask did not permit.

Widening the masks without the quoting change would extend the same failure to \ and ', so
both parts are here together.

Verification

I rendered the template twice against the same stubbed configuration, once from master and
once patched, and diffed the generated file:

  • the output line count is unchanged, so the macro's whitespace control adds nothing
  • exactly four lines differ, and each is an intended value
  • Tunnel-Password = tun%nel pass, becomes Tunnel-Password = 'tun%nel pass',

I then parsed each rendered value back under FreeRADIUS single-quoted string rules and checked
that the original is recovered byte for byte:

  • %, %{User-Name} and ${...} sequences
  • backslash, single quote, both together, and a trailing backslash
  • an embedded space, which the unquoted form truncated
  • the full printable ASCII range, 0x20 through 0x7E
  • the 1 and 128 character boundaries

On a live OPNsense 26.7.3_8 install running os-freeradius 1.10.2 and FreeRADIUS 3.2.10, with
the single-quoted rendering applied, a 62 character password containing % and $$ changed
from Access-Reject to Access-Accept, and a second user whose password was unaffected continued
to authenticate.

Not changed here

clients.conf, proxy.conf and mods-enabled-sql write secrets with a comparable weakness,
reported separately in #5696. Those three model fields carry no mask, so an operator can have
stored an already-escaped value under the guidance given in #1655, and changing the rendering
would repair raw secrets while breaking escaped ones. The fields this pull request touches
have always been masked against \ and ", so no escaped value can exist for them and the
change carries no such trade-off.


Related issue

Closes #5678.

FreeRADIUS applies dynamic expansion to double-quoted strings, so a password containing % failed to parse and rlm_files discarded the whole user entry. The user was then rejected for having no Auth-Type, which is indistinguishable from a wrong password. Render the password as a single-quoted string, escaping the backslash and single quote that single-quoted strings honor.

Widen the password mask to printable ASCII, keeping the 128 character limit, and align the field help text with it.
Both Tunnel-Password attributes were rendered with no quoting, so they failed on % in the same way as the user password and additionally ended at the first space. Render them single-quoted with the same escaping, and widen the tunnel_password and fallback_tunnel_password masks to printable ASCII to match.
@dotCooCoo dotCooCoo changed the title net/freeradius: quote user passwords literally so % is not expanded net/freeradius: quote password values literally so % is not expanded Sep 2, 2026
Replace the repeated inline escaping with a q() macro and apply it to the remaining check items on the user line, Max-Daily-Session, Simultaneous-Use and Login-Time. Those three cannot currently hold a % given their masks, so they go through the macro to keep one rule in the file rather than a per-field judgment about which masks are safe.
<label>Password</label>
<type>password</type>
<help><![CDATA[Set the password for the user. Allowed characters are 0-9, a-z, A-Z, and ,._-!$%/()+#=:& with up to 128 characters.]]></help>
<help><![CDATA[Set the password for the user. Any printable ASCII character is allowed, with up to 128 characters.]]></help>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW: feels spurious to mention all characters are supported

@@ -1,9 +1,10 @@
{%- macro q(value) -%}'{{ value | replace('\\', '\\\\') | replace("'", "\\'") }}'{%- endmacro -%}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not really looking forward to reviewing if this is safe. Depending on concerns this may miss the bar for inclusion to not introduce other issues. In these cases a simpler fix may be the better approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

net/freeradius: Some Cleartext-Password values containing % fail PAP authentication

2 participants