Skip to content

Out-of-bounds read in `nutdrv_qx` Ippon USB subdriver reply parsing

Low
jimklimov published GHSA-3f8v-rv78-m298 Aug 27, 2026

Package

nut

Affected versions

>=2.7.3, <=2.8.5

Patched versions

2.8.6

Description

Summary

ippon_command() in drivers/nutdrv_qx.c can read past the end of its 64-byte stack reply buffer. A full-length USB reply containing neither a carriage return (0x0D) nor a NUL byte (0x00) overwrites the complete zero-initialized buffer and leaves it unterminated. The bounded carriage-return scan then finds nothing, and the fallback calls strlen() on that buffer.

The resulting out-of-bounds length is also passed to upsdebug_hex() before it is clamped, and the following text debug statement independently calls unbounded strcspn() on the same buffer. Depending on the surrounding stack contents, this can crash the driver or read adjacent stack bytes into debug output and the caller's reply buffer. I have not established a write primitive or code execution.

Triggering the condition requires a faulty or attacker-controlled USB device, or an emulator, selected for the Ippon transport to return the malformed full-length reply.

Affected versions

The vulnerable fallback was introduced by commit b707432942e396db82b9191da1bc3694b7ea4950 on 2014-09-03. It is present in
the stable tags v2.7.3, v2.7.4, and v2.8.0 through v2.8.5, as well as the reviewed master baseline 26177060be94a738bf2d612441af264c90ac37a9. The first affected stable release therefore appears to be v2.7.3.

The USB ID table automatically selects this transport for Phoenixtec 06da:0003, documented there as Mustek Powermust. It can also be selected explicitly with subdriver = ippon.

Details

The reply buffer is:

char tmp[64];

It is initially cleared for command transmission, but a successful full-size
read overwrites all 64 bytes:

ret = usb_interrupt_read(udev,
    0x81,
    (usb_ctrl_charbuf)tmp, sizeof(tmp), 1000);

The carriage-return scan itself is bounded by ret:

for (i = 0, len = 0; i < (size_t)ret; i++) {
    if (tmp[i] != '\r')
        continue;

    len = ++i;
    break;
}

If a 64-byte reply contains no carriage return, len remains zero. If it also
contains no NUL byte, this fallback reads beyond tmp:

if (!len)
    len = strlen(tmp);

The out-of-bounds result is then used before any clamp:

upsdebug_hex(5, "read", tmp, (size_t)len);
upsdebugx(3, "read: %.*s", (int)strcspn(tmp, "\r"), tmp);

upsdebug_hex() may therefore read and log bytes beyond tmp. The strcspn() call independently scans beyond the buffer until it happens to find a carriage return or NUL byte.

Only afterwards does the code limit the copy length:

len = len < buflen ? len : buflen - 1;

memset(buf, 0, buflen);
memcpy(buf, tmp, len);

The normal caller, qx_process(), passes a 511-byte destination. A strlen() result between 65 and that destination limit can therefore copy adjacent stack bytes into the caller's reply buffer. The clamp does not restore the source bound and does not protect either debug read above.

PoC

I do not have Ippon hardware or an emulated proof of concept and have not tried to demonstrate a crash or memory disclosure on a live system.

The condition follows directly from the source when usb_interrupt_read() returns 64 bytes, all non-zero and none equal to 0x0D: the bounded scan leaves len == 0, and strlen(tmp) receives a non-terminated 64-byte array.

A focused reproduction could mock usb_interrupt_read() to return 64 bytes with no carriage return and no NUL, then call ippon_command() under ASan or Valgrind. I can prepare such a test if it would help triage.

Impact

This is an out-of-bounds read (CWE-125). Exploitation requires control of, emulation of, or a fault in the attached USB device selected for the Ippon transport. Potential consequences are:

  • a driver crash, disrupting UPS monitoring; and
  • disclosure of adjacent driver stack bytes through debug output or data copied into the caller's reply buffer.

No memory write outside the intended destination and no code execution have been demonstrated.

Suggested direction

When no carriage return is found, determine the payload length only within the ret bytes: use a bounded NUL search such as memchr(tmp, '\0', ret), or treat all ret bytes as payload if neither terminator is present. Use that bounded length for hex/text logging and for the destination copy. A regression test should cover a 64-byte reply containing neither carriage return nor NUL.

Discovery context and AI disclosure

This was found while deriving a separate OMRON USB transport for the same driver. The OMRON implementation uses bounded memchr()/length handling and does not modify ippon_command().

AI assistance from Claude and OpenAI Codex was used during discovery and drafting. The source-level control flow, affected-version history and this report were reviewed and are submitted by the human reporter.

Severity

Low

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Physical
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
Low
Integrity
None
Availability
Low

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:L

CVE ID

No known CVE

Weaknesses

Out-of-bounds Read

The product reads data past the end, or before the beginning, of the intended buffer. Learn more on MITRE.

Improper Null Termination

The product does not terminate or incorrectly terminates a string or array with a null character or equivalent terminator. Learn more on MITRE.

Credits