fix(server): validate packet length header before parsing#158
Conversation
The server crashes with an unhandled ValueError whenever a client sends a length header that isn't a valid integer (e.g. after a connection hiccup desyncs the length-prefixed framing, as reported with VPN/multi-network client setups). This kills that client's handler thread with an unstructured traceback instead of a clear error. Wrap the header parse in a try/except ValueError, log a clear error identifying the client and the malformed header, and close just that connection. Add a regression test that sends a raw non-integer header and verifies the server logs clearly, closes only that connection, and keeps serving other clients. Closes BOSWatch#155.
|
@KoenigMjr Can you take a look on this PR please. |
|
My main question regarding this error is: Is this a general issue or a temporary one? If so, does it affect the entire TCP socket asynchronously, or is only that specific packet "cut off"? Does the connection need to be closed, or is it sufficient to simply skip the faulty packet? After all, as far as I know, this only happens with keep-alive heartbeat packets, right? |
|
It's fantastic to see multiple perspectives on this. @AshleyAHuang did an amazing job pinpointing the exact symptom (the ValueError crash) and providing a top-tier regression test, which we should absolutely adopt into the final codebase regardless of which implementation we choose. To answer your questions about the root cause and compare our approaches, here is a breakdown of what happens IMHO:- Is it a general or temporary issue? - Does it affect the entire socket or just one packet? - Does it only happen to keep-alive? - Can we just skip the packet, or must the connection close? The approach in this PR wraps the header parsing in a try/except ValueError. (Approach A)
In my parallel overhaul, we approached this from a "Zero Packet Loss" perspective. (Approach B)
Proposal for Moving ForwardBoth solutions agree on the vulnerability, but Approach B rewrites the network I/O layer to prevent data loss over shaky VPNs entirely, rather than just handling the disconnect gracefully. However, @AshleyAHuang regression test is excellent. It elegantly validates that a bad header won't crash the server. My recommendation: I would love to see us combine the best of both worlds. We could adapt @AshleyAHuang test suite to ensure it guards our recvall() implementation as well. I leave the final architectural decision to you, @Schrolli91. Let me know if you would like me to link our branch or push our recvall + [ack] implementation here for comparison. Approach B ist beta-testing atm. Its running local at my server, but i dont use any VPN-connection, but I've teamed up with a beta-tester running a VPN client setup. It seems to run smoothly so far. |
|
@KoenigMjr Your solution sounds great—it tackles the problem at its root rather than just reacting with basic error handling (like dropping frames). Let's go with the recvall solution. If that works for you, I’ll merge this now as a temporary hotfix. Any merge conflicts on your end should be easy to fix since the changes are minimal. Once that's done, you can adapt the test for your approach |
|
LGTM aswell :) Good to Go! :) |
|
@AshleyAHuang Thank you for the contribution :) |
Closes #155.
Root cause
_ThreadedTCPRequestHandler.handle()parses the 10-byte length header with a bareint(header.strip()). If the TCP stream ever gets desynced (as reported, apparently from connection instability over VPN/multi-network client setups), the bytes read as "header" aren't a valid integer, and the unhandledValueErrorkills that client's handler thread with an unstructured traceback — matching the exact error in the issue (invalid literal for int() with base 10: 'alive>12').Fix
Wrapped the header parse in
try/except ValueError, logging a clear error identifying the client and the malformed header content, then closing just that connection cleanly — instead of an unhandled exception.Testing
Added
test_serverInvalidHeaderClosesConnectionOnly: opens a raw socket, sends a 10-byte non-integer header (b"bad_head!!"), and asserts the server (1) logs a clear error, (2) closes that connection, (3) keeps working normally for other clients afterward.Verified non-vacuously: reverting just the fix reproduces the exact reported traceback and the test fails; restoring it, the test passes. Full suite: 65 passed / 4 skipped (one unrelated broadcast-socket test is flaky in this sandboxed environment specifically — confirmed pre-existing by re-running in isolation and re-running the full suite clean, unrelated to this change).
flake8clean on both changed files.Note on a related, separate finding
While investigating, the linked comment thread surfaced a
SyntaxErrorcrash inpacket.py, which parses incoming packet data viaeval(str(bwPacket.strip()))— i.e.eval()on raw network input. That's a distinct, more serious concern (arbitrary code execution risk from untrusted socket data) than the header-validation bug here, so I've deliberately left it out of this PR. Happy to open a separate issue/PR for it if useful.