From 1d69f44bfac04854ae9bd55213ff5ea3cd6f8974 Mon Sep 17 00:00:00 2001
From: Fujii Masao <fujii@postgresql.org>
Date: Thu, 13 Aug 2026 00:50:26 +0900
Subject: [PATCH v2 2/3] Avoid returning oom_buffer from psql slash option
 scanner

psql_scan_slash_option() builds option text in a local PQExpBufferData
and returns the buffer's data pointer to its caller. If the initial
allocation or a later enlargement failed, that data pointer could be the
static PQExpBuffer OOM buffer rather than malloc-owned storage.

Detect a broken option buffer before returning it, report OOM, and return
NULL instead. Also avoid evaluating a backtick substitution when the option
buffer is already broken, since doing so could otherwise touch the static
OOM buffer.

This keeps the existing NULL-return convention for slash options. Callers
are not generally changed to distinguish OOM from no option.

Also make \lo_export require both parsed arguments before calling
do_lo_export(), so an OOM-induced NULL first argument is not passed to
atooid().
---
 src/bin/psql/command.c       |  2 +-
 src/bin/psql/psqlscanslash.l | 24 ++++++++++++++++++++++--
 2 files changed, 23 insertions(+), 3 deletions(-)

diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index f80839266e6..b91fd85c977 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2383,7 +2383,7 @@ exec_command_lo(PsqlScanState scan_state, bool active_branch, const char *cmd)
 
 		if (strcmp(cmd + 3, "export") == 0)
 		{
-			if (!opt2)
+			if (!opt1 || !opt2)
 			{
 				pg_log_error("\\%s: missing required argument", cmd);
 				success = false;
diff --git a/src/bin/psql/psqlscanslash.l b/src/bin/psql/psqlscanslash.l
index 9640d6e6a6e..298473afda8 100644
--- a/src/bin/psql/psqlscanslash.l
+++ b/src/bin/psql/psqlscanslash.l
@@ -529,7 +529,8 @@ psql_scan_slash_command(PsqlScanState state)
 
 /*
  * Parse off the next argument for a backslash command, and return it as a
- * malloc'd string.  If there are no more arguments, returns NULL.
+ * malloc'd string.  If there are no more arguments or on out-of-memory,
+ * returns NULL.
  *
  * type tells what processing, if any, to perform on the option string;
  * for example, if it's a SQL identifier, we want to downcase any unquoted
@@ -606,6 +607,16 @@ psql_scan_slash_option(PsqlScanState state,
 	 */
 	Assert(lexresult == LEXRES_EOL || lexresult == LEXRES_OK);
 
+	/*
+	 * yylex() appends option text to mybuf, so a buffer enlargement failure
+	 * during lexing can leave mybuf broken even if initialization succeeded.
+	 */
+	if (PQExpBufferDataBroken(mybuf))
+	{
+		pg_log_error("out of memory");
+		return NULL;
+	}
+
 	switch (final_state)
 	{
 		case xslashargstart:
@@ -816,7 +827,7 @@ static void
 evaluate_backtick(PsqlScanState state)
 {
 	PQExpBuffer output_buf = state->output_buf;
-	char	   *cmd = output_buf->data + backtick_start_offset;
+	char	   *cmd;
 	PQExpBufferData cmd_output;
 	FILE	   *fd;
 	bool		error = false;
@@ -824,6 +835,15 @@ evaluate_backtick(PsqlScanState state)
 	char		buf[512];
 	size_t		result;
 
+	/*
+	 * The option buffer is already broken; avoid touching the static
+	 * oom_buffer and let psql_scan_slash_option() return NULL.
+	 */
+	if (PQExpBufferBroken(output_buf))
+		return;
+
+	cmd = output_buf->data + backtick_start_offset;
+
 	initPQExpBuffer(&cmd_output);
 
 	fflush(NULL);
-- 
2.55.0

