[GH-ISSUE #44] H1-H4 values displayed incorrectly by awg showconf when using AWG 1.x kernel module #19

Open
opened 2026-10-13 21:02:27 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @wvolkov on GitHub (Apr 3, 2026).
Original GitHub issue: https://github.com/amnezia-vpn/amneziawg-tools/issues/44

Originally assigned to: @ygurov on GitHub.

H1-H4 values displayed incorrectly by awg showconf when using AWG 1.x kernel module

Summary

awg showconf displays H1-H4 values as raw uint32 decimal numbers instead of the original string values when communicating with AWG 1.x kernel module. This causes tools like WGDashboard to read the wrong values and write them back to .conf, corrupting the configuration.

Environment

  • Kernel module: amneziawg v1.0.0 (AWG 1.x, genetlink version 1)
  • Tools: amneziawg-tools v1.0.20210914 (latest from master)
  • OS: Debian, kernel 6.18.9-zabbly+

Steps to Reproduce

ip link add test0 type amneziawg
awg setconf test0 <(echo "[Interface]
PrivateKey = $(awg genkey)
ListenPort = 51820
H1 = 112
H2 = 358
H3 = 1321
H4 = 3455")

awg showconf test0 | grep H
ip link delete test0

Expected Output

H1 = 112
H2 = 358
H3 = 1321
H4 = 3455

Actual Output

H1 = 3289393
H2 = 3683635
H3 = 1321
H4 = 3455

Root Cause Analysis

The problem is in ipc-linux.h, in the GET_MAGIC_HEADER macro (line ~38):

#define GET_MAGIC_HEADER(attr, magic_header, attr_name)\
    if (!mnl_attr_validate(attr, MNL_TYPE_U32)) {\
        uint32_t numeric_value = mnl_attr_get_u32(attr);\
        magic_header = malloc(12);\
        ...
        snprintf(magic_header, 12, "%u", numeric_value);\   // <-- HERE

When the kernel module reports version < 2 (AWG 1.x), the tools:

  1. On write (put_magic_header_attr): parse the string "112", convert to uint32 = 112, send via netlink as NLA_U32
  2. On read (GET_MAGIC_HEADER): receive uint32 from kernel, convert back to string with snprintf("%u", ...)

This works correctly when the kernel stores and returns the numeric value 112.

However, the kernel module appears to store the H value as raw bytes of the ASCII string. So when tools send uint32 = 112 (0x00000070), the kernel stores 0x00000070. But when reading back, the kernel returns 0x00323131 (which is the bytes '1','1','2','\0' in little-endian) = 3289393 decimal.

This can be verified:

import struct
struct.pack('<I', 3289393)  # → b'112\x00'
struct.pack('<I', 3683635)  # → b'358\x00'

Values that fit in a single byte (1, 5) or happen to be large enough to not be visually affected (1000, 65536) appear to work correctly, masking the bug for many configurations.

Impact

This is a data corruption bug when used with management tools:

  1. User sets H1 = 112 in .conf
  2. awg-quick up applies it correctly — tunnel works
  3. A management tool (e.g. WGDashboard) reads awg showconf → sees H1 = 3289393
  4. Tool writes H1 = 3289393 back to .conf
  5. Next awg-quick up sends 3289393 as uint32 → kernel now has a different value
  6. Clients configured with H1=112 can no longer connect

This also affects awg-quick save, which calls awg showconf internally — so even without a dashboard, any awg-quick save or awg-quick down (with SaveConfig = true) will corrupt H values in the .conf file.

Suggested Fix

In GET_MAGIC_HEADER, when reading uint32 from AWG 1.x kernel, check if the bytes represent an ASCII string and decode accordingly. Or, ensure that put_magic_header_attr and GET_MAGIC_HEADER use a consistent encoding (both numeric or both string).

Alternatively, showconf.c already uses printf("H1 = %s\n", device->init_packet_magic_header) which would work correctly if magic_header contained the original string — the conversion in GET_MAGIC_HEADER via snprintf("%u", ...) is what breaks it.

Originally created by @wvolkov on GitHub (Apr 3, 2026). Original GitHub issue: https://github.com/amnezia-vpn/amneziawg-tools/issues/44 Originally assigned to: @ygurov on GitHub. # H1-H4 values displayed incorrectly by `awg showconf` when using AWG 1.x kernel module ## Summary `awg showconf` displays H1-H4 values as raw uint32 decimal numbers instead of the original string values when communicating with AWG 1.x kernel module. This causes tools like WGDashboard to read the wrong values and write them back to .conf, corrupting the configuration. ## Environment - Kernel module: amneziawg v1.0.0 (AWG 1.x, genetlink version 1) - Tools: amneziawg-tools v1.0.20210914 (latest from `master`) - OS: Debian, kernel 6.18.9-zabbly+ ## Steps to Reproduce ```bash ip link add test0 type amneziawg awg setconf test0 <(echo "[Interface] PrivateKey = $(awg genkey) ListenPort = 51820 H1 = 112 H2 = 358 H3 = 1321 H4 = 3455") awg showconf test0 | grep H ip link delete test0 ``` ## Expected Output ``` H1 = 112 H2 = 358 H3 = 1321 H4 = 3455 ``` ## Actual Output ``` H1 = 3289393 H2 = 3683635 H3 = 1321 H4 = 3455 ``` ## Root Cause Analysis The problem is in `ipc-linux.h`, in the `GET_MAGIC_HEADER` macro (line ~38): ```c #define GET_MAGIC_HEADER(attr, magic_header, attr_name)\ if (!mnl_attr_validate(attr, MNL_TYPE_U32)) {\ uint32_t numeric_value = mnl_attr_get_u32(attr);\ magic_header = malloc(12);\ ... snprintf(magic_header, 12, "%u", numeric_value);\ // <-- HERE ``` When the kernel module reports version < 2 (AWG 1.x), the tools: 1. **On write** (`put_magic_header_attr`): parse the string "112", convert to uint32 = 112, send via netlink as `NLA_U32` 2. **On read** (`GET_MAGIC_HEADER`): receive uint32 from kernel, convert back to string with `snprintf("%u", ...)` This works correctly when the kernel stores and returns the **numeric value** 112. However, the kernel module appears to store the H value as raw bytes of the ASCII string. So when tools send uint32 = 112 (0x00000070), the kernel stores 0x00000070. But when reading back, the kernel returns 0x00323131 (which is the bytes `'1','1','2','\0'` in little-endian) = 3289393 decimal. This can be verified: ```python import struct struct.pack('<I', 3289393) # → b'112\x00' struct.pack('<I', 3683635) # → b'358\x00' ``` Values that fit in a single byte (1, 5) or happen to be large enough to not be visually affected (1000, 65536) appear to work correctly, masking the bug for many configurations. ## Impact This is a **data corruption** bug when used with management tools: 1. User sets `H1 = 112` in .conf 2. `awg-quick up` applies it correctly — tunnel works 3. A management tool (e.g. WGDashboard) reads `awg showconf` → sees `H1 = 3289393` 4. Tool writes `H1 = 3289393` back to .conf 5. Next `awg-quick up` sends 3289393 as uint32 → kernel now has a **different** value 6. Clients configured with H1=112 can no longer connect This also affects `awg-quick save`, which calls `awg showconf` internally — so even without a dashboard, any `awg-quick save` or `awg-quick down` (with `SaveConfig = true`) will corrupt H values in the .conf file. ## Suggested Fix In `GET_MAGIC_HEADER`, when reading uint32 from AWG 1.x kernel, check if the bytes represent an ASCII string and decode accordingly. Or, ensure that `put_magic_header_attr` and `GET_MAGIC_HEADER` use a consistent encoding (both numeric or both string). Alternatively, `showconf.c` already uses `printf("H1 = %s\n", device->init_packet_magic_header)` which would work correctly if `magic_header` contained the original string — the conversion in `GET_MAGIC_HEADER` via `snprintf("%u", ...)` is what breaks it.
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
amnezia-vpn/amneziawg-tools#19
No description provided.