Repository navigation
Qodo: full review of the codebase (#34) - #35
compot-dev wants to merge 1 commit into
Conversation
Restores the code removed in qodo-base; the tree equals main.
PR Summary by QodoExpose the complete VTesse codebase for review
AI Description
Diagram
High-Level Assessment
Files changed (78)
|
Code Review by Qodo
1. Renewed leases keep obsolete routes
|
| { | ||
| int has_addr = netif_is_up(n) && !ip4_addr_isany_val(*netif_ip4_addr(n)); | ||
|
|
||
| if (has_addr && !st.bound) { |
There was a problem hiding this comment.
1. Renewed leases keep obsolete routes 🐞 Bug ≡ Correctness
status_cb() applies pending classless routes only when st.bound is false, even though the DHCP hook records routes from renewal acknowledgments. When a renewal changes or removes routes while the interface remains bound, subsequent traffic continues using the previous route table.
Agent Prompt
## Issue description
DHCP renewal acknowledgments update pending classless routes, but the stack applies them only on initial address binding.
## Fix Focus Areas
- netcore/stack.c[110-132]
- netcore/stack.c[210-229]
## Recommended Fix
Apply the routes associated with each accepted DHCP acknowledgment even when the address remains bound, and clear routes when a renewal no longer supplies classless routes. Add a renewal test that changes the route set without changing the interface address.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| px->conns[px->nconns++] = c; | ||
| px->st.accepted++; | ||
| px->st.active++; | ||
| if (which == 0) { | ||
| c->socks = 1; | ||
| c->state = C_GREETING; | ||
| } else { |
There was a problem hiding this comment.
2. Idle clients can exhaust proxy slots 🐞 Bug ☼ Reliability
accept_on() places SOCKS clients in handshake states without a deadline, and the proxy's timeout handling covers only connecting clients. A client that opens sockets and leaves their handshakes incomplete can occupy every max_conns slot, preventing other clients from connecting.
Agent Prompt
## Issue description
Incomplete SOCKS handshakes retain connection slots indefinitely.
## Fix Focus Areas
- netcore/proxy.c[528-552]
- netcore/proxy.c[634-665]
## Recommended Fix
Assign a deadline when accepting a SOCKS client, include handshake states in timeout scheduling and expiry, and add a test that opens incomplete handshakes until their deadlines pass.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| r = calloc(1, sizeof(*r)); | ||
| if (!r) { | ||
| snprintf(err, err_size, "udp relay: out of memory"); | ||
| return NULL; | ||
| } | ||
| r->fd = socket(AF_INET, SOCK_DGRAM, 0); |
There was a problem hiding this comment.
3. Udp socket failures close standard input 🐞 Bug ☼ Reliability
nc_udp_relay_create() leaves the zero-initialized r->fd unchanged when socket() fails, then passes the relay to a destructor that closes every nonnegative descriptor. Under socket-creation failure, this closes descriptor 0, which may be standard input or another resource owned by the host process.
Agent Prompt
## Issue description
Failed UDP socket creation routes a zero-initialized descriptor through cleanup, closing descriptor 0.
## Fix Focus Areas
- netcore/udprelay.c[67-75]
- netcore/udprelay.c[97-105]
## Recommended Fix
Set `r->fd = -1` immediately after allocation, before any operation that can enter the cleanup path. Test socket-creation failure while descriptor 0 is open.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| static void run_hook(const char *event) | ||
| { | ||
| pid_t pid; | ||
|
|
||
| reap_children(); |
There was a problem hiding this comment.
4. Dhcp can restart after the client exits 🐞 Bug ☼ Reliability
run_hook() forks up and stop hooks without waiting or ordering them. If the stop hook checks for the DHCP PID file before the earlier up hook creates it, the up hook can subsequently start a background DHCP client that the stopped CLI no longer controls.
Agent Prompt
## Issue description
Asynchronously launched interface hooks can execute out of order, leaving DHCP running after shutdown.
## Fix Focus Areas
- cli/vtesse_cli.c[519-550]
- cli/vtesse_cli.c[888-894]
- scripts/vtesse-hook.sh[25-34]
- scripts/vtesse-hook.sh[69-75]
## Recommended Fix
Track the up-hook child and ensure it finishes before running the stop hook, or otherwise serialize hook execution and wait for stop cleanup before CLI exit.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| if [ -n "$list" ]; then | ||
| set -- $list | ||
| while [ $# -ge 2 ]; do | ||
| ip route add "$1" via "$2" dev "$interface" && echo "$1 $2" >> "$STATE" |
There was a problem hiding this comment.
5. On-link dhcp routes fail to install 🐞 Bug ≡ Correctness
routes_add() always includes via "$2" when installing a classless route, including when DHCP supplies 0.0.0.0 as the gateway for an on-link destination. That route needs to be installed directly on the interface; the failed command is omitted from the script's route state and retried with the same error on renewal.
Agent Prompt
## Issue description
The DHCP script tries to install on-link classless routes through a zero gateway.
## Fix Focus Areas
- scripts/vtesse-udhcpc.script[19-33]
## Recommended Fix
For a `0.0.0.0` gateway, add and remove the route using `dev "$interface"` without `via`; retain gateway routing for other entries. Cover both forms in a script test.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| check("greeting is exactly 50 bytes, NUL padded", | ||
| len(g[0]) == 50 and g[0].startswith(b"VTUN server ver ") and g[0].rstrip(b"\0").endswith(b"\n")) | ||
| check("main session flags <TeK>", reply == "OK FLAGS: <TeK>\n", repr(reply)) | ||
| check("ECHO_REQ is the first frame after OK FLAGS", wait_flag(s, ECHO_REQ, 2)) |
There was a problem hiding this comment.
11. The first-frame test can pass incorrectly 🐞 Bug ≡ Correctness
wait_flag() discards complete data frames while looking for the requested flag, but its caller uses success to assert that an echo request was the first frame. If a data frame arrives before that request, the self-test still passes the ordering check.
Agent Prompt
## Issue description
The self-test's first-frame assertion ignores earlier data frames.
## Fix Focus Areas
- bench/selftest.py[65-91]
- bench/selftest.py[128-133]
## Recommended Fix
Read the first complete frame after authentication and assert that its flag is `ECHO_REQ`, rather than searching past earlier frames.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| for t in test_proto test_route test_client; do | ||
| echo "== $QEMU_BIN $t" | ||
| "$QEMU_BIN" "build-$TARGET-full/$t" | tail -1 | ||
| done |
There was a problem hiding this comment.
9. Failing cross-target unit tests still pass ci 🐞 Bug ☼ Reliability
ci/build-musl.sh runs each QEMU unit test as "$QEMU_BIN" ... | tail -1 under set -eu with no pipefail, so the pipeline's exit status is the status of tail, not of the test. In the CI job (QEMU=1 ci/build-musl.sh in build.yml), a test that fails on arm/mips/arm64 under qemu exits nonzero but the build carries on and goes green.
Agent Prompt
## Issue description
QEMU unit test failures are masked by `| tail -1` in ci/build-musl.sh, because the pipeline status is that of `tail`.
## Fix Focus Areas
- ci/build-musl.sh[94-98]
## Recommended Fix
Save the output and exit status separately, for example: `out=$("$QEMU_BIN" "build-$TARGET-full/$t" 2>&1) || { echo "$out"; exit 1; }; echo "$out" | tail -1`. Another option is to drop the `tail` entirely.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| echo "downloading $URL" | ||
| # download first (with retries), then extract: a truncated stream piped | ||
| # into tar looks like a corrupt archive | ||
| curl -fsSL --retry 3 -o "$CACHE/toolchain.tar" "$URL" | ||
| tar -xf "$CACHE/toolchain.tar" -C "$CACHE" | ||
| rm -f "$CACHE/toolchain.tar" | ||
| [ -x "$DIR/relocate-sdk.sh" ] && "$DIR/relocate-sdk.sh" >/dev/null 2>&1 || true |
There was a problem hiding this comment.
10. Release toolchains are downloaded unverified 🐞 Bug ⛨ Security
ci/build-musl.sh downloads the cross toolchain archive with curl and runs tar -xf on it, then runs its compiler (and relocate-sdk.sh), with no checksum or signature check. If a mirror, redirect or the cache directory is tampered with, the compiler it supplies builds every published static binary, while the vtund source download in server/build-vtund.sh does get pinned to a SHA256.
Agent Prompt
## Issue description
Toolchain archives are downloaded and executed without any integrity check.
## Fix Focus Areas
- ci/build-musl.sh[31-61]
## Recommended Fix
Add an expected SHA256 for each TARGET in the case statement (next to URL). After the curl, run `echo "$SHA256 $CACHE/toolchain.tar" | sha256sum -c -` and abort if it fails, before extracting the archive.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| stop() { | ||
| printf "Stopping vtesse: " | ||
| start-stop-daemon -K -q -p "$PIDFILE" && echo "OK" || echo "not running" | ||
| rm -f "$PIDFILE" | ||
| } |
There was a problem hiding this comment.
12. Init stop can kill an unrelated process 🐞 Bug ☼ Reliability
stop() in S99vtesse calls start-stop-daemon -K -q -p "$PIDFILE" without -x "$DAEMON", so it signals whatever process now holds the PID stored in the file. status uses the same bare `kill -0. If vtesse crashed and left /var/run/vtesse.pid` behind, and the PID was later reused, stop/restart signals that other process and status reports vtesse as running.
Agent Prompt
## Issue description
S99vtesse stop/status trust the PID file without checking the process identity.
## Fix Focus Areas
- scripts/S99vtesse[25-40]
## Recommended Fix
Use `start-stop-daemon -K -q -p "$PIDFILE" -x "$DAEMON"` in stop(). In status, also check that `/proc/$pid/exe` (or the cmdline) matches $DAEMON before reporting that it is running.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| else if (!strcmp(argv[i], "--seconds") && i + 1 < argc) | ||
| seconds = atof(argv[++i]); |
There was a problem hiding this comment.
13. A mistyped run time makes the client run forever 🐞 Bug ≡ Correctness
--seconds is parsed with atof(), which gives no error, so a non-numeric or negative value such as --seconds 1m turns into 0 or less. nc_loop_run then gets deadline 0 (`seconds > 0 ? ... : 0`), and the client runs with no time limit instead of rejecting the argument.
Agent Prompt
## Issue description
`--seconds` accepts garbage and negative values silently, which disables the timeout.
## Fix Focus Areas
- cli/vtesse_cli.c[601-602]
## Recommended Fix
Parse with `strtod(arg, &end)`. Reject the argument (usage error, exit 2) if `end == arg`, `*end != '\0'` or the value is `<= 0`.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
|
Qodo → triage: 12 of 13 fixed in #36 (1, 2, 4–13). Declined: 3 (UDP socket failure closes stdin) — not a bug: |
Understood. finding 3 is being declined as a false positive: |
Qodo reviewed the full tree in PR #35 and reported 13 bugs; 12 are fixed here, one is not a bug (udprelay: r->fd is set to -1 by the failed socket() call before the cleanup path, so descriptor 0 is never closed). - netcore/stack: apply option 121/249 routes of every DHCPACK while bound (renewals keep the address, so status_cb never saw them) - vtun_client: only complete frames refresh last_rx; negative tuning values are rejected; reconnect backoff doubling cannot overflow - proxy: SOCKS5 handshakes get a deadline (connect timeout) - cli: checked parsing of --seconds and the --fetch port; the stop hook waits for earlier hooks and is itself waited for - vtesse-udhcpc.script: on-link classless routes without "via 0.0.0.0" - S99vtesse: stop/status match the daemon binary (-x) - ci/build-musl.sh: QEMU test failures fail the build; toolchain archives are verified (pinned SHA256, GitHub digest for the rolling OpenIPC asset) - bench/selftest.py: assert the first frame itself is ECHO_REQ - tests: negative timer settings, a never-finished frame times out Refs #34
|
Review done: findings fixed in #36 (merged, v1.12). Review-only PR, closing; helper branches deleted. |
Review-only PR: the code went to
mainwithout pull requests, so Qodo never saw it (#34).qodo-baseismainwith the project code removed (third_party/, docs, README and CHANGELOG stay); this branch restores it, so the diff is the whole project code as of a00f6ab (~9.8k lines: vtun client, netcore/lwIP glue, proxy, CLI, Android app and JNI, bench, CI, scripts).Findings will be fixed in separate PRs against
main. Do not merge; both helper branches are deleted when the review is done.Refs #34