Re: psql: avoid over-reading unterminated prompt escapes

From: Chao Li <li(dot)evan(dot)chao(at)gmail(dot)com>
To: Kirill Reshke <reshkekirill(at)gmail(dot)com>
Cc: PostgreSQL Hackers <pgsql-hackers(at)postgresql(dot)org>
Subject: Re: psql: avoid over-reading unterminated prompt escapes
Date: 2026-09-21 07:35:03
Message-ID: 1C0EE251-6929-4319-9763-9D51BF048A12@gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

> On Sep 21, 2026, at 13:04, Kirill Reshke <reshkekirill(at)gmail(dot)com> wrote:
>
> On Mon, 21 Sept 2026 at 08:31, Chao Li <li(dot)evan(dot)chao(at)gmail(dot)com> wrote:
>>
>> Hi,
>>
>> I happened to encounter a psql crash that could not be reproduced reliably with a normal build. However, AddressSanitizer reproduces it consistently.
>>
>> 1. Build psql with AddressSanitizer
>> ```
>> % ./configure CFLAGS='-O1 -g -fsanitize=address -fno-omit-frame-pointer' LDFLAGS='-fsanitize=address’
>> % make -C src/bin/psql psql
>> ```
>> Note: On my MacBook, gcc points to clang.
>>
>> 2. Run psql and set PROMPT1 to an unterminated variable
>> ```
>> evantest=# \set PROMPT1 '%:aa'
>> =================================================================
>> ==34121==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000001f35 at pc 0x0001024be288 bp 0x00016d976930 sp 0x00016d976928
>> READ of size 1 at 0x602000001f35 thread T0
>> #0 0x0001024be284 in get_prompt prompt.c:103
>> #1 0x0001024bc1a8 in MainLoop mainloop.c:166
>> #2 0x0001024ce05c in main startup.c:471
>> #3 0x0001827ac4e0 in start+0x1b4c (dyld:arm64e+0x204e0)
>>
>> 0x602000001f35 is located 0 bytes after 5-byte region [0x602000001f30,0x602000001f35)
>> allocated by thread T0 here:
>> #0 0x000103172b54 in strdup+0x108 (libclang_rt.asan_osx_dynamic.dylib:arm64e+0x3ab54)
>> #1 0x0001025080e4 in pg_strdup fe_memutils.c:101
>> #2 0x0001024dcf5c in SetVariable variables.c:316
>> #3 0x0001024952e4 in exec_command_set command.c:2923
>> #4 0x00010248b8b8 in exec_command command.c:445
>> #5 0x0001024889e4 in HandleSlashCmds command.c:260
>> #6 0x0001024bcb70 in MainLoop mainloop.c:499
>> #7 0x0001024ce05c in main startup.c:471
>> #8 0x0001827ac4e0 in start+0x1b4c (dyld:arm64e+0x204e0)
>>
>> SUMMARY: AddressSanitizer: heap-buffer-overflow prompt.c:103 in get_prompt
>> Shadow bytes around the buggy address:
>> 0x602000001c80: fa fa 03 fa fa fa 02 fa fa fa 00 03 fa fa 06 fa
>> 0x602000001d00: fa fa 02 fa fa fa 00 02 fa fa 00 02 fa fa 00 05
>> 0x602000001d80: fa fa 00 02 fa fa 00 07 fa fa 02 fa fa fa 00 02
>> 0x602000001e00: fa fa 00 02 fa fa 00 02 fa fa 00 02 fa fa 00 02
>> 0x602000001e80: fa fa 00 07 fa fa 00 fa fa fa 00 04 fa fa fd fa
>> =>0x602000001f00: fa fa fd fa fa fa[05]fa fa fa fd fa fa fa fa fa
>> 0x602000001f80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
>> 0x602000002000: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
>> 0x602000002080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
>> 0x602000002100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
>> 0x602000002180: 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
>> ==34121==ABORTING
>> zsh: abort psql -d evantest
>> ```
>>
>> The problem is that the current code assumes a terminating “:" exists. When it does, "p += nameend + 1" makes p point to the terminating colon, and the for loop's increment advances p to the string's terminating '\0'. When the terminating colon is absent, the same assignment already makes p point to '\0', and the for loop's increment advances p one past the end of the string. The next loop condition then dereferences p out of bounds. If that invalid read yields a nonzero value, the loop continues and can perform further out-of-bounds reads.
>>
>> The fix is straightforward, only advance over the terminating colon when it exists. The same problem also exists for the %\command`` escape.
>>
>> See the attached patch for details.
>>
>> Best regards,
>> --
>> Chao Li (Evan)
>> HighGo Software Co., Ltd.
>> https://www.highgo.com/
>>
>
>
> Hi!
> I think this is indeed a real issue, please register CF item for this.
> Looks like this code is dating back to a45195a191 [0], so this bug
> exists in all supported versions.

Thank you very much for the review. Yes, this is an old bug.

>
> Code fix itself is fine but maybe write it like `if p[1] != NULL` for
> consistency.
>

Make sense. We should use ‘\0’ instead of NULL.

>
> [0] https://github.com/postgres/postgres/blob/a45195a191eec367a4f305bb71ab541d17a3b9f9/src/bin/psql/prompt.c#L227
>
> --
> Best regards,
> Kirill Reshke

PFA v2 - addressed Kirill’s comment.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

Attachment Content-Type Size
v2-0001-psql-Avoid-over-reading-unterminated-prompt-escap.patch application/octet-stream 1.5 KB

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Zhijie Hou 2026-09-21 08:33:03 Re: Logical replication can lose an update after concurrent index invalidation
Previous Message Michael Paquier 2026-09-21 07:30:59 Re: [Patch] Fix pg_get_multixact_stats() over-reporting members on a hot standby