Skip to content

fix(docker): parse a /proc port at 16 bits, not 32 (CodeQL alert 8) - #343

Merged
ako merged 2 commits into
mainfrom
fix/portowner-hexport-bitsize
Aug 29, 2026
Merged

fix(docker): parse a /proc port at 16 bits, not 32 (CodeQL alert 8)#343
ako merged 2 commits into
mainfrom
fix/portowner-hexport-bitsize

Conversation

@ako

@ako ako commented Aug 29, 2026

Copy link
Copy Markdown
Owner

Closes CodeQL alert 8, go/incorrect-integer-conversion (high), at cmd/mxcli/docker/portowner_linux.go:121.

What it is

hexPort reads the port out of a /proc/net/tcp local_address field:

n, err := strconv.ParseUint(addr[i+1:], 16, 32)
...
return int(n), true

Parsing at 32 bits and narrowing to int is only correct where int is 64 bits. Measured with a GOARCH=386 binary (which runs on an amd64 host, so this is observed rather than argued):

input width 32 (before) width 16 (after)
0100007F:1F90 8080, true 8080, true
0100007F:FFFFFFFF @ 64-bit 4294967295, true 0, false
0100007F:FFFFFFFF @ 32-bit -1, true 0, false

How bad it is

Not exploitable, and I'd rate it informational rather than high:

  • the input is /proc/net/tcp, written by the kernel, which formats the port %04X — no value wider than 16 bits is ever there, and nothing user-supplied reaches this function;
  • the consequence of a wrap would be p != port failing to match, so the port-owner lookup returns nothing and the guard falls back to its generic "port in use" message. No memory unsafety, no bypass;
  • the 32-bit half is doubly unreachable today: the repo does not currently build for 386 at all (mdl/settingsoverlay/settingsoverlay.go:45 has 9007199254740992 as an untyped int constant).

Why fix it anyway

A TCP port is 16 bits, so 16 is simply the right width, and parsing at the target type's width moves the range check into the parser instead of leaving it as a rule the next caller has to remember. It's a one-token change, and the alert otherwise stays open.

Tests

Both new tests fail against the old parser at 64 bits — verified by reverting the fix:

--- FAIL: TestHexPort_RefusesAValueWiderThanAPort
    0100007F:FFFFFFFF: accepted as port 4294967295 — a port is 16 bits
    0100007F:80000000: accepted as port 2147483648 — a port is 16 bits
    0100007F:10000: accepted as port 65536 — a port is 16 bits
--- FAIL: TestParseListeningInodes_IgnoresAnOutOfRangePortField
    port 4294967295 matched a row whose port field is not a port: map[5555:true]

TestHexPort_RefusesAValueWiderThanAPort opens with the control that a real port (8080) still parses, so the fix can't pass by refusing everything. The out-of-range literal is written int(^uint32(0)) rather than 4294967295, which is not a valid int constant on a 32-bit build.

Symptom row appended to .claude/skills/fix-issue.md.

🤖 Generated with Claude Code

hexPort parsed the port field of /proc/net/tcp at bit size 32 and
converted the result to int. That is only correct where int is 64 bits.
On a 32-bit build the conversion wraps silently: measured with a
GOARCH=386 binary, "0100007F:FFFFFFFF" comes back as -1 with ok=true,
and at 64 bits as 4294967295 — in both cases a number the caller then
compares against a port.

A TCP port is 16 bits and the kernel writes the field as %04X, so
nothing it produces reaches the wrap and no hostile input gets here; the
consequence would be a missed match, and the port-owner diagnostic
falling back to its generic message. Parsing at the type's real width
makes the range check the parser's job rather than a rule the next
caller has to remember.

Closes CodeQL alert 8 (go/incorrect-integer-conversion).

Tests fail against the old parser at 64 bits: hexPort accepts
FFFFFFFF/80000000/10000, and parseListeningInodes matches a row whose
port field is not a port. A control asserts a real port still parses.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ako
ako merged commit a7d8dfd into main Aug 29, 2026
13 of 14 checks passed
ako pushed a commit that referenced this pull request Aug 31, 2026
`filter($L, Amount > 0)` was stored as `Microflows$FilterByExpression` with
the authored text verbatim. Mendix evaluates that expression once per item
with the item bound to `$currentObject`, where a bare attribute name is not
valid — so mxbuild reported CE0117 while `mxcli check` passed.

This is the unfinished half of bug #343. That fix rerouted `attr = value` to
`Microflows$Filter` (filter BY ATTRIBUTE), which takes a member name rather
than an expression and so accepts the bare form. Every other predicate still
fell through to the expression shape, which made the split turn on the
OPERATOR and be invisible to the author: `Status = 'x'` built and
`Status != 'x'` did not.

Not a Mendix version change, despite the report: the same 19-microflow probe
produces the identical 7 errors on mxbuild 11.11.0 and 11.13.0.

- A bare name that provably resolves to a member of the list's element entity
  is rewritten to `$currentObject/<member>` (an association keeps its module
  qualifier, an attribute does not). A name that does not resolve is refused
  rather than left to surface as CE0117. When the element entity cannot be
  determined nothing is proven either way, so the predicate is passed through.
- The predicate can reach the builder as a frozen `SourceExpr`, so the rewrite
  patches the source text too, skipping single-quoted literals — the `'Amount'`
  in `filter($L, Qty > 0 and Status != 'Amount')` must survive untouched.
- MDL-LISTOP01 refuses an iterator variable that is not in scope, pre-empting
  CE0109. It keys on scope rather than on the name, so `$item` stays valid as
  an enclosing loop's iterator — the shape of CLAUDE.md's O(N) `find` idiom.
- The `syntax microflow.list-operations` example no longer teaches a form that
  only compiles through the `=` reroute, and notes that SORT is not an
  expression, so a bare attribute is the only spelling there.

Control: stubbing the qualifier takes the repro from 0 to 7 × CE0117 with the
two `=` cases staying green, which is also the #343 regression guard.

Repros: mdl-examples/bug-tests/1002-filter-find-bare-attribute.mdl (0 errors
on mxbuild 11.13.0) and 1002-filter-bad-iterator.fail.mdl.

Closes mendixlabs#1002

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017s476QkXr9CFMvKspVzcvu
ako pushed a commit that referenced this pull request Sep 1, 2026
`filter($L, Amount > 0)` was stored as `Microflows$FilterByExpression` with
the authored text verbatim. Mendix evaluates that expression once per item
with the item bound to `$currentObject`, where a bare attribute name is not
valid — so mxbuild reported CE0117 while `mxcli check` passed.

This is the unfinished half of bug #343. That fix rerouted `attr = value` to
`Microflows$Filter` (filter BY ATTRIBUTE), which takes a member name rather
than an expression and so accepts the bare form. Every other predicate still
fell through to the expression shape, which made the split turn on the
OPERATOR and be invisible to the author: `Status = 'x'` built and
`Status != 'x'` did not.

Not a Mendix version change, despite the report: the same 19-microflow probe
produces the identical 7 errors on mxbuild 11.11.0 and 11.13.0.

- A bare name that provably resolves to a member of the list's element entity
  is rewritten to `$currentObject/<member>` (an association keeps its module
  qualifier, an attribute does not). A name that does not resolve is refused
  rather than left to surface as CE0117. When the element entity cannot be
  determined nothing is proven either way, so the predicate is passed through.
- The predicate can reach the builder as a frozen `SourceExpr`, so the rewrite
  patches the source text too, skipping single-quoted literals — the `'Amount'`
  in `filter($L, Qty > 0 and Status != 'Amount')` must survive untouched.
- MDL-LISTOP01 refuses an iterator variable that is not in scope, pre-empting
  CE0109. It keys on scope rather than on the name, so `$item` stays valid as
  an enclosing loop's iterator — the shape of CLAUDE.md's O(N) `find` idiom.
- The `syntax microflow.list-operations` example no longer teaches a form that
  only compiles through the `=` reroute, and notes that SORT is not an
  expression, so a bare attribute is the only spelling there.

Control: stubbing the qualifier takes the repro from 0 to 7 × CE0117 with the
two `=` cases staying green, which is also the #343 regression guard.

Repros: mdl-examples/bug-tests/1002-filter-find-bare-attribute.mdl (0 errors
on mxbuild 11.13.0) and 1002-filter-bad-iterator.fail.mdl.

Closes mendixlabs#1002

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017s476QkXr9CFMvKspVzcvu
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant