security: fix panic on untrusted input (Phase 2 Critical)

- Replace all sqlite::open().unwrap() per-request with shared Arc<Mutex<Connection>>
- Add Mutex poisoning recovery in all cfg.lock() and db.lock() calls
- Fix std::str::from_utf8().unwrap() with safe match → 400 Bad Request
- Fix timestamp_nanos_opt().unwrap() with safe match
- Fix panic on RPC client failure (bal-pusher): error log + sleep + retry
- Fix ZMQ socket connect with retry loop (bal-pusher)
- Add ZMQ_RCVTIMEO=5000 and match recv for graceful timeout (bal-pusher)
- Fix ZMQ subscribe error with match instead of unwrap (bal-pusher)
- Add unwrap_or for all tbl_stats row fields in echo_stats (prevent NULL panic)
- Add panic_regression_tests.rs: test mutex poisoning recovery and NULL unwrap_or

All tests pass: cargo test --test panic_regression_tests + sql_injection_tests
Build verified: cargo check --bin=bal-server --bin=bal-pusher
This commit is contained in:
2026-07-16 14:49:31 -04:00
parent df8effcc60
commit 0fdefcfd0f
3 changed files with 162 additions and 37 deletions

View File

@@ -312,7 +312,9 @@ async fn main_result(cfg: &MyConfig, network_params: &NetworkParams) -> Result<(
let _ = calculate_stats(&db, network_params.db_field.clone()).await;
}
Err(erx) => {
panic!("impossible to get client {}", erx)
error!("impossible to get client: {}, retrying on next block", erx);
thread::sleep(Duration::from_secs(5));
return Ok(());
}
}
Ok(())
@@ -622,17 +624,38 @@ async fn main() -> std::io::Result<()> {
let zmq_address = network_params.zmq_listener.clone();
info!("zmq listening on: {}", zmq_address);
socket.connect(&zmq_address).unwrap();
loop {
match socket.connect(&zmq_address) {
Ok(_) => break,
Err(e) => {
error!("ZMQ connect failed: {}, retrying in 5s...", e);
thread::sleep(Duration::from_secs(5));
}
}
}
socket.set_subscribe(b"").unwrap();
match socket.set_subscribe(b"") {
Ok(_) => {},
Err(e) => {
error!("ZMQ subscribe failed: {}, exiting", e);
return Ok(());
}
}
let _ = main_result(&cfg, network_params).await;
info!("waiting new blocks..");
let mut last_seq: Vec<u8> = [0; 4].to_vec();
let mut counter = 0;
let max = 100;
socket.set_rcvtimeo(5000).unwrap(); // 5 seconds timeout
loop {
let message = socket.recv_multipart(0).unwrap();
let message = match socket.recv_multipart(0) {
Ok(m) => m,
Err(e) => {
warn!("ZMQ recv timeout or error: {}, retrying...", e);
continue;
}
};
let topic = message[0].clone();
let body = message[1].clone();
let seq = message[2].clone();