mirror of
https://github.com/amnezia-vpn/amneziawg-tools.git
synced 2026-10-13 21:34:42 +00:00
[GH-ISSUE #71] u16_range_from_string accepts values above UINT16_MAX and silently truncates AWG 3.x ranges #33
Labels
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
amnezia-vpn/amneziawg-tools#33
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @2Grey on GitHub (Aug 31, 2026).
Original GitHub issue: https://github.com/amnezia-vpn/amneziawg-tools/issues/71
Summary
u16_range_from_string()validates parsed values againstUINT32_MAX, but stores them in a range composed of twouint16_tvalues.As a result, values from
65536through4294967295are accepted and silently truncated modulo 65536.This affects AWG 3.x timing, padding and persistent-keepalive parameters parsed by both configuration files and the
awg setcommand.Affected source
amnezia-vpn/amneziawg-toolsv3.1.20260812src/type.cu16_range_from_string()Source:
https://github.com/amnezia-vpn/amneziawg-tools/blob/v3.1.20260812/src/type.c#L40-L59
Root cause
The parser uses
unsigned long:but validates both values against
UINT32_MAX:The result is then passed to:
However,
u16_range_init()takesuint16_targuments:https://github.com/amnezia-vpn/amneziawg-tools/blob/v3.1.20260812/src/type.h#L32-L43
The implicit conversion to
uint16_tsilently discards the upper bits.Examples
655356553565536065537170000446465535-6553665535-070000-700104464-4474429496729565535The ascending-range check happens before truncation:
Therefore an input that is ascending before conversion can become descending after conversion. For example:
becomes:
Affected configuration parameters
u16_range_from_string()is used for:ContentPaddingAdditionRekeyAfterTimeRekeyTimeoutRejectAfterTimeKeepaliveTimeoutMaxHandshakeAttemptsPersistentKeepaliveReferences:
https://github.com/amnezia-vpn/amneziawg-tools/blob/v3.1.20260812/src/config.c#L552-L575
https://github.com/amnezia-vpn/amneziawg-tools/blob/v3.1.20260812/src/config.c#L587-L599
The same parser is also used by the corresponding
awg setcommand-line options.Reproduction
Create an AmneziaWG interface:
Set a value immediately above the
uint16_tlimit:The command is expected to reject the value, but the parser accepts it and stores zero.
Read it back:
Expected:
Actual:
Another useful case is:
The parser accepts an ascending input, but after truncation the packed range becomes equivalent to:
Clean up:
A kernel module is not required to demonstrate the parser bug itself. It can also be reproduced with a small unit test:
Actual output:
Impact
Silent configuration corruption
A value such as:
is silently converted to zero. Since zero commonly means “use the default”, the running configuration no longer matches the supplied configuration.
This is difficult to diagnose because
awg setconfsucceeds.Invalid packed ranges
Inputs crossing the 65535 boundary can become descending ranges after conversion:
The kernel does not validate the ordering when accepting the packed
NLA_U32timer attributes.Its range selector calls:
with the truncated values. A descending range can therefore underflow the random-range calculation and produce values unrelated to the original configuration.
Client/server mismatch
If another implementation correctly rejects the value or uses a wider type, the two peers may end up with different timer or padding behavior despite receiving the same textual configuration.
Expected behavior
All inputs above
UINT16_MAXshould be rejected before constructingu16_range_t.For example:
No accepted input should be able to produce a range where:
after conversion.
Suggested fix
Validate against
UINT16_MAX:and:
It would also be safer to handle
strtoul()overflow explicitly:The same should be done before parsing the upper bound.
Suggested tests
Add parser tests for at least:
The tests should verify both the parser return value and the resulting
lo/hivalues for accepted inputs.