mirror of
https://github.com/joyieldInc/predixy.git
synced 2026-02-05 01:42:24 +08:00
Guard response error log underflow in parser
This commit is contained in:
parent
2b9ea1030a
commit
a47a3c8cc0
@ -245,10 +245,12 @@ ResponseParser::Status ResponseParser::parse(Buffer* buf, int& pos)
|
|||||||
SString<64> bufHex;
|
SString<64> bufHex;
|
||||||
bufHex.printHex(buf->data() + pos, buf->length() - pos);
|
bufHex.printHex(buf->data() + pos, buf->length() - pos);
|
||||||
SString<16> errHex;
|
SString<16> errHex;
|
||||||
errHex.printHex(cursor - 1, end - cursor + 1);
|
// Clamp errStart to the buffer head to avoid underflow on first-byte errors.
|
||||||
|
const char* errStart = cursor > buf->data() ? cursor - 1 : cursor;
|
||||||
|
errHex.printHex(errStart, end - errStart);
|
||||||
logError("response parse error %d state %d buf:%s errpos %d err:%s",
|
logError("response parse error %d state %d buf:%s errpos %d err:%s",
|
||||||
error, mState, bufHex.data(),
|
error, mState, bufHex.data(),
|
||||||
cursor - 1 - buf->data() - pos, errHex.data());
|
(errStart - buf->data()) - pos, errHex.data());
|
||||||
return ParseError;
|
return ParseError;
|
||||||
}
|
}
|
||||||
pos = cursor + 1 - buf->data();
|
pos = cursor + 1 - buf->data();
|
||||||
|
|||||||
131
test/response_parser_error_log.py
Normal file
131
test/response_parser_error_log.py
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
# Start a temporary predixy instance with a fake backend that returns
|
||||||
|
# invalid responses to exercise response parser error logging safely.
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
import socket
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
from test_util import parse_args, exit_with_result
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_port(host, port, timeout=5.0):
|
||||||
|
deadline = time.time() + timeout
|
||||||
|
while time.time() < deadline:
|
||||||
|
try:
|
||||||
|
with socket.create_connection((host, port), timeout=0.5):
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
time.sleep(0.05)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def start_fake_backend(host="127.0.0.1"):
|
||||||
|
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
server.bind((host, 0))
|
||||||
|
server.listen(1)
|
||||||
|
port = server.getsockname()[1]
|
||||||
|
|
||||||
|
def handler():
|
||||||
|
try:
|
||||||
|
conn, _ = server.accept()
|
||||||
|
conn.recv(1024)
|
||||||
|
conn.sendall(b"!")
|
||||||
|
conn.close()
|
||||||
|
finally:
|
||||||
|
server.close()
|
||||||
|
|
||||||
|
thread = threading.Thread(target=handler, daemon=True)
|
||||||
|
thread.start()
|
||||||
|
return port
|
||||||
|
|
||||||
|
|
||||||
|
def start_predixy(root, backend_port):
|
||||||
|
predixy_bin = os.path.join(root, "src", "predixy")
|
||||||
|
if not os.path.exists(predixy_bin):
|
||||||
|
raise RuntimeError("predixy binary not found")
|
||||||
|
|
||||||
|
listen_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
listen_sock.bind(("127.0.0.1", 0))
|
||||||
|
listen_port = listen_sock.getsockname()[1]
|
||||||
|
listen_sock.close()
|
||||||
|
|
||||||
|
tmp_dir = tempfile.TemporaryDirectory()
|
||||||
|
conf_path = os.path.join(tmp_dir.name, "predixy_test.conf")
|
||||||
|
with open(conf_path, "w") as f:
|
||||||
|
f.write(
|
||||||
|
"Name PredixyRespParserTest\n"
|
||||||
|
f"Bind 127.0.0.1:{listen_port}\n"
|
||||||
|
"WorkerThreads 1\n"
|
||||||
|
"ClientTimeout 3\n"
|
||||||
|
"LogVerbSample 0\n"
|
||||||
|
"LogDebugSample 0\n"
|
||||||
|
"LogInfoSample 10000\n"
|
||||||
|
"LogNoticeSample 1\n"
|
||||||
|
"LogWarnSample 1\n"
|
||||||
|
"LogErrorSample 1\n"
|
||||||
|
"\n"
|
||||||
|
"StandaloneServerPool {\n"
|
||||||
|
" RefreshMethod fixed\n"
|
||||||
|
" Group test {\n"
|
||||||
|
f" + 127.0.0.1:{backend_port}\n"
|
||||||
|
" }\n"
|
||||||
|
"}\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
proc = subprocess.Popen([predixy_bin, conf_path],
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL)
|
||||||
|
if not wait_for_port("127.0.0.1", listen_port, timeout=5.0):
|
||||||
|
proc.terminate()
|
||||||
|
tmp_dir.cleanup()
|
||||||
|
raise RuntimeError("predixy did not start")
|
||||||
|
|
||||||
|
return proc, listen_port, tmp_dir
|
||||||
|
|
||||||
|
|
||||||
|
def run_test(project_root):
|
||||||
|
backend_port = start_fake_backend()
|
||||||
|
proc, predixy_port, tmp_dir = start_predixy(project_root, backend_port)
|
||||||
|
try:
|
||||||
|
sock = socket.create_connection(("127.0.0.1", predixy_port), timeout=1.0)
|
||||||
|
sock.sendall(b"*1\r\n$4\r\nping\r\n")
|
||||||
|
try:
|
||||||
|
sock.recv(16)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
sock.close()
|
||||||
|
|
||||||
|
time.sleep(0.2)
|
||||||
|
if proc.poll() is not None:
|
||||||
|
print("FAIL: predixy exited after invalid backend response")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
with socket.create_connection(("127.0.0.1", predixy_port), timeout=1.0):
|
||||||
|
pass
|
||||||
|
except Exception as exc:
|
||||||
|
print("FAIL: predixy not accepting connections:", exc)
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
proc.terminate()
|
||||||
|
try:
|
||||||
|
proc.wait(timeout=2.0)
|
||||||
|
except Exception:
|
||||||
|
proc.kill()
|
||||||
|
tmp_dir.cleanup()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
args = parse_args("Response parser error logging test")
|
||||||
|
root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||||
|
success = run_test(root)
|
||||||
|
exit_with_result(success, "response parser error logging",
|
||||||
|
"response parser error logging")
|
||||||
@ -142,6 +142,7 @@ TESTS=(
|
|||||||
"test/null_response_handling.py"
|
"test/null_response_handling.py"
|
||||||
"test/request_parser_boundary.py"
|
"test/request_parser_boundary.py"
|
||||||
"test/request_parser_error_log.py"
|
"test/request_parser_error_log.py"
|
||||||
|
"test/response_parser_error_log.py"
|
||||||
"test/pubsub_long_name.py"
|
"test/pubsub_long_name.py"
|
||||||
"test/pubsub_large_message.py"
|
"test/pubsub_large_message.py"
|
||||||
"test/transaction_forbid.py"
|
"test/transaction_forbid.py"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user