Skip to content

Fix out-of-bounds read in GIN key comparison - #62

Open
PGZXB wants to merge 1 commit into
postgrespro:masterfrom
PGZXB:fix-issue-58
Open

PGZXB wants to merge 1 commit into
postgrespro:masterfrom
PGZXB:fix-issue-58

Conversation

@PGZXB

@PGZXB PGZXB commented Sep 25, 2026

Copy link
Copy Markdown

Fixes #58

Fixes the memory-safety issue reported in #58: Crashes and Memory Problems.

Fix

Adds the missing bounds/validity check at the faulting site.

diff --git a/jsonb_gin_ops.c b/jsonb_gin_ops.c
index 7addd3b..c8fd726 100644
--- a/jsonb_gin_ops.c
+++ b/jsonb_gin_ops.c
@@ -487,6 +487,9 @@ make_value_path_entry_handler(ExtractedNode *node, Pointer extra)
 static int32
 compare_gin_key_value(GINKey *arg1, GINKey *arg2)
 {
+	if (VARSIZE_ANY(arg1) < GINKEYLEN || VARSIZE_ANY(arg2) < GINKEYLEN)
+		elog(ERROR, "GINKey must be at least %zu bytes", (size_t) GINKEYLEN);
+
 	if (GINKeyType(arg1) != GINKeyType(arg2))
 	{
 		return (GINKeyType(arg1) > GINKeyType(arg2)) ? 1 : -1;
@@ -549,6 +552,11 @@ gin_compare_jsonb_value_path(PG_FUNCTION_ARGS)
 	GINKey	   *arg2 = (GINKey *)PG_GETARG_VARLENA_P(1);
 	int32		result = 0;
 
+	if (VARSIZE_ANY(arg1) < GINKEYLEN || VARSIZE_ANY(arg2) < GINKEYLEN)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid GINKey: argument is too small to be a GINKey")));
+
 	result = compare_gin_key_value(arg1, arg2);
 	if (result == 0 && arg1->hash != arg2->hash)
 	{
@@ -567,6 +575,11 @@ gin_compare_partial_jsonb_value_path(PG_FUNCTION_ARGS)
 	StrategyNumber strategy = PG_GETARG_UINT16(2);
 	int32		result;
 
+	if (VARSIZE_ANY(partial_key) < GINKEYLEN || VARSIZE_ANY(key) < GINKEYLEN)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid GINKey: argument is too small to be a GINKey")));
+
 	if (strategy == JsQueryMatchStrategyNumber)
 	{
 		KeyExtra *extra = (KeyExtra *)PG_GETARG_POINTER(3);

Verification Before Fix

eUserMain /src/postgresql-18.0/src/backend/tcop/postgres.c:4168
    #23 0x5606e5dd5a1e in main /src/postgresql-18.0/src/backend/main/main.c:223
    #24 0x7f303a2f91c9  (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: a4a7992a8e66555c8141ab2a08a8465ff6e0ea65)
    #25 0x7f303a2f928a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: a4a7992a8e66555c8141ab2a08a8465ff6e0ea65)
    #26 0x5606e56d46b4 in _start (/opt/pgasan/bin/postgres+0x41c6b4) (BuildId: dac6646e6d8b4c5d7be374588919952fa6114135)

0x525000045900 is located 0 bytes after 8192-byte region [0x525000043900,0x525000045900)
allocated by thread T0 here:
    #0 0x7f303a6c89c7 in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
    #1 0x5606e6639516 in AllocSetContextCreateInternal /src/postgresql-18.0/src/backend/utils/mmgr/aset.c:444
    #2 0x5606e625d2ed in PostgresMain /src/postgresql-18.0/src/backend/tcop/postgres.c:4351
    #3 0x5606e6262be0 in PostgresSingleUserMain /src/postgresql-18.0/src/backend/tcop/postgres.c:4168
    #4 0x5606e5dd5a1e in main /src/postgresql-18.0/src/backend/main/main.c:223
    #5 0x7f303a2f91c9  (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: a4a7992a8e66555c8141ab2a08a8465ff6e0ea65)
    #6 0x7f303a2f928a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: a4a7992a8e66555c8141ab2a08a8465ff6e0ea65)
    #7 0x5606e56d46b4 in _start (/opt/pgasan/bin/postgres+0x41c6b4) (BuildId: dac6646e6d8b4c5d7be374588919952fa6114135)

SUMMARY: AddressSanitizer: heap-buffer-overflow /src/jsquery/jsonb_gin_ops.c:490 in compare_gin_key_value
Shadow bytes around the buggy address:
  0x525000045680: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x525000045700: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x525000045780: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x525000045800: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x525000045880: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x525000045900:[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x525000045980: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x525000045a00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x525000045a80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x525000045b00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x525000045b80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==118==ABORTING

Verification After Fix

ixing permissions on existing directory /tmp/jsq_pgdata ... ok
creating subdirectories ... ok
selecting dynamic shared memory implementation ... posix
selecting default "max_connections" ... 100
selecting default "shared_buffers" ... 128MB
selecting default time zone ... Etc/UTC
creating configuration files ... ok
running bootstrap script ... ok
performing post-bootstrap initialization ... ok
syncing data to disk ... ok

initdb: warning: enabling "trust" authentication for local connections
initdb: hint: You can change this by editing pg_hba.conf or using the option -A, or --auth-local and --auth-host, the next time you run initdb.

Success. You can now start the database server using:

    /opt/pgasan/bin/pg_ctl -D /tmp/jsq_pgdata -l logfile start


PostgreSQL stand-alone backend 18.0
backend> backend> 2026-09-25 00:50:01.107 UTC [1828] LOG:  checkpoint starting: shutdown immediate
2026-09-25 00:50:01.834 UTC [1828] LOG:  checkpoint complete: wrote 89 buffers (0.5%), wrote 3 SLRU buffers; 0 WAL file(s) added, 0 removed, 0 recycled; write=0.035 s, sync=0.609 s, total=0.757 s; sync files=46, longest=0.045 s, average=0.014 s; distance=518 kB, estimate=518 kB; lsn=0/176EEB8, redo lsn=0/176EEB8

PostgreSQL stand-alone backend 18.0
backend> 2026-09-25 00:50:02.014 UTC [1832] ERROR:  invalid GINKey: argument is too small to be a GINKey
2026-09-25 00:50:02.014 UTC [1832] STATEMENT:  SELECT public.gin_compare_jsonb_value_path('\x00C5603C4015B6B6D6922683B30BE89B89263AD046FC29F3D60D94B58F758D6B5A301D1970DFDA2283A66A88A14020C16203767335473D011BF0B65A483113'::bytea,'\xB898'::bytea);
	
backend> 2026-09-25 00:50:02.052 UTC [1832] LOG:  checkpoint starting: shutdown immediate
2026-09-25 00:50:02.228 UTC [1832] LOG:  checkpoint complete: wrote 2 buffers (0.0%), wrote 3 SLRU buffers; 0 WAL file(s) added, 0 removed, 0 recycled; write=0.041 s, sync=0.050 s, total=0.214 s; sync files=4, longest=0.027 s, average=0.013 s; distance=13 kB, estimate=13 kB; lsn=0/1772400, redo lsn=0/1772400

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Crashes and Memory Problems

1 participant