Oversized PostgreSQL startup frame can terminate the Cube service
A pre-authentication PostgreSQL length prefix became an unchecked allocation size
CVSS 3.1 base score
HighVector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Summary
Source
read_contents()
rust/cubesql/pg-srv/src/buffer.rs:95
Sink
read_contents()
rust/cubesql/pg-srv/src/buffer.rs:116
Any network client reaching Cube's optional PostgreSQL SQL interface could send a four-byte frame length near 0xffffffff, causing the native service to attempt a multi-gigabyte allocation before authentication and terminate under ordinary memory limits.
Root cause. read_contents() accepted an untrusted unsigned protocol length, subtracted the header size, and resized a Vec to the declared body length without a server-owned maximum or fallible reserve. The same primitive affected startup, authentication, and later frontend packets.
Remediation evidence. Cube acknowledged the disclosure and merged exact PR #11339 on 23 July 2026. The patch bounds PostgreSQL startup, authentication, and authenticated frontend frames before allocation and adds boundary regression coverage.
Severity
- Attack vectorAV
- Network
- Attack complexityAC
- Low
- Privileges requiredPR
- None
- User interactionUI
- None
- ScopeS
- Unchanged
- ConfidentialityC
- None
- IntegrityI
- None
- AvailabilityA
- High
Metric values as published in the disclosure vector. Meters show how far each value raises exposure.
Source-to-sink trace
- Source · attacker-controlledrust/
cubesql/ pg-srv/ src/ buffer.rs:95 read_contents()
- Step 01rust/
cubesql/ cubesql/ src/ sql/ postgres/ service.rs:34 Enabling
pgSqlPortexposes the PostgreSQL-compatible listener, typically on all interfaces.rust let listener = TcpListener::bind(self.address.clone()).await?; - Step 02rust/
cubesql/ cubesql/ src/ sql/ postgres/ shim.rs:208 A newly accepted socket enters initial-message parsing before any database authentication succeeds.
rust let (initial_parameters, auth_method) = self.process_initial_message().await?; - Step 03rust/
cubesql/ pg-srv/ src/ buffer.rs:95 The vulnerable reader converted the attacker length to
usizeand resized a buffer to the complete declared body.rust let body_length = reader.read_u32().await? - 4; let mut buffer = vec![0; body_length as usize]; reader.read_exact(&mut buffer).await?; - Sinkrust/
cubesql/ pg-srv/ src/ buffer.rs:116 read_contents()
Impact
Reported impact
A single unauthenticated connection can terminate a memory-constrained Cube SQL service, and a few concurrent requests can exhaust a larger host. This is a direct remote availability impact with no prerequisite account.
Attack surface
Cube's optional PostgreSQL-compatible SQL listener when reachable from an untrusted network.
Preconditions
The listener is enabled and network-reachable. The attacker needs no Cube account, PostgreSQL credential, or victim interaction.
Attack path
- 1
Open a TCP connection to Cube's PostgreSQL port.
- 2
Send a four-byte startup-frame length near
0xffffffffwithout the body. - 3
The server allocates according to the untrusted declaration before authentication.
- 4
Under a normal process/container memory limit, the service aborts or is OOM-killed.
Proof of concept
- 01
Environment setup
Run the affected Cube SQL server in a disposable container with a conservative memory limit and expose its test PostgreSQL port only to the lab client.
- 02
Target configuration
Set
CUBEJS_PG_SQL_PORTand confirm a legitimate client can connect before the test. - 03
Exploit delivery
Open a raw TCP socket and send the big-endian bytes
ff ff ff ff, then keep the connection open without providing the declared body. - 04
Expected response
The vulnerable process attempts the multi-gigabyte allocation and exits or is killed by the container's memory controller; a subsequent legitimate connection fails.
- 05
Outcome
PR #11339 rejects the frame as oversized before reserving memory, closes the malicious connection, and keeps the service available.
Remediation
Guidance
Upgrade to a Cube release containing PR #11339. Validate every untrusted PostgreSQL frame length against phase-specific limits before allocation and use fallible reservation plus read timeouts.
let length = (reader.read_u32().await? - 4) as usize;let mut buffer = vec![0; length];let body_length = reader.read_u32().await?.checked_sub(4).ok_or(invalid)?;if body_length > MAX_FRONTEND_MESSAGE_SIZE { return Err(too_large); }let mut buffer = Vec::new();buffer.try_reserve_exact(body_length as usize)?;
Check the upstream record for the project's current remediation status.
Investigate the paths that matter in your codebase.
Winfunc can trace relevant code paths, preserve supporting evidence, and prepare remediation suggestions for engineering review within an agreed scope.
