mirror of
https://github.com/postgres/postgres.git
synced 2026-02-18 10:09:35 -05:00
An incorrectly-encoded multibyte character near the end of a string could cause various processing loops to run past the string's terminating NUL, with results ranging from no detectable issue to a program crash, depending on what happens to be in the following memory. This isn't an issue in the server, because we take care to verify the encoding of strings before doing any interesting processing on them. However, that lack of care leaked into client-side code which shouldn't assume that anyone has validated the encoding of its input. Although this is certainly a bug worth fixing, the PG security team elected not to regard it as a security issue, primarily because any untrusted text should be sanitized by PQescapeLiteral or the like before being incorporated into a SQL or psql command. (If an app fails to do so, the same technique can be used to cause SQL injection, with probably much more dire consequences than a mere client-program crash.) Those functions were already made proof against this class of problem, cf CVE-2006-2313. To fix, invent PQmblenBounded() which is like PQmblen() except it won't return more than the number of bytes remaining in the string. In HEAD we can make this a new libpq function, as PQmblen() is. It seems imprudent to change libpq's API in stable branches though, so in the back branches define PQmblenBounded as a macro in the files that need it. (Note that just changing PQmblen's behavior would not be a good idea; notably, it would completely break the escaping functions' defense against this exact problem. So we just want a version for those callers that don't have any better way of handling this issue.) Per private report from houjingyi. Back-patch to all supported branches.
167 lines
4.2 KiB
C
167 lines
4.2 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* common.c
|
|
* Common support routines for bin/scripts/
|
|
*
|
|
*
|
|
* Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
* src/bin/scripts/common.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres_fe.h"
|
|
|
|
#include <signal.h>
|
|
#include <unistd.h>
|
|
|
|
#include "common.h"
|
|
#include "common/connect.h"
|
|
#include "common/logging.h"
|
|
#include "common/string.h"
|
|
#include "fe_utils/cancel.h"
|
|
#include "fe_utils/query_utils.h"
|
|
#include "fe_utils/string_utils.h"
|
|
|
|
/*
|
|
* Split TABLE[(COLUMNS)] into TABLE and [(COLUMNS)] portions. When you
|
|
* finish using them, pg_free(*table). *columns is a pointer into "spec",
|
|
* possibly to its NUL terminator.
|
|
*/
|
|
void
|
|
splitTableColumnsSpec(const char *spec, int encoding,
|
|
char **table, const char **columns)
|
|
{
|
|
bool inquotes = false;
|
|
const char *cp = spec;
|
|
|
|
/*
|
|
* Find the first '(' not identifier-quoted. Based on
|
|
* dequote_downcase_identifier().
|
|
*/
|
|
while (*cp && (*cp != '(' || inquotes))
|
|
{
|
|
if (*cp == '"')
|
|
{
|
|
if (inquotes && cp[1] == '"')
|
|
cp++; /* pair does not affect quoting */
|
|
else
|
|
inquotes = !inquotes;
|
|
cp++;
|
|
}
|
|
else
|
|
cp += PQmblenBounded(cp, encoding);
|
|
}
|
|
*table = pnstrdup(spec, cp - spec);
|
|
*columns = cp;
|
|
}
|
|
|
|
/*
|
|
* Break apart TABLE[(COLUMNS)] of "spec". With the reset_val of search_path
|
|
* in effect, have regclassin() interpret the TABLE portion. Append to "buf"
|
|
* the qualified name of TABLE, followed by any (COLUMNS). Exit on failure.
|
|
* We use this to interpret --table=foo under the search path psql would get,
|
|
* in advance of "ANALYZE public.foo" under the always-secure search path.
|
|
*/
|
|
void
|
|
appendQualifiedRelation(PQExpBuffer buf, const char *spec,
|
|
PGconn *conn, bool echo)
|
|
{
|
|
char *table;
|
|
const char *columns;
|
|
PQExpBufferData sql;
|
|
PGresult *res;
|
|
int ntups;
|
|
|
|
splitTableColumnsSpec(spec, PQclientEncoding(conn), &table, &columns);
|
|
|
|
/*
|
|
* Query must remain ABSOLUTELY devoid of unqualified names. This would
|
|
* be unnecessary given a regclassin() variant taking a search_path
|
|
* argument.
|
|
*/
|
|
initPQExpBuffer(&sql);
|
|
appendPQExpBufferStr(&sql,
|
|
"SELECT c.relname, ns.nspname\n"
|
|
" FROM pg_catalog.pg_class c,"
|
|
" pg_catalog.pg_namespace ns\n"
|
|
" WHERE c.relnamespace OPERATOR(pg_catalog.=) ns.oid\n"
|
|
" AND c.oid OPERATOR(pg_catalog.=) ");
|
|
appendStringLiteralConn(&sql, table, conn);
|
|
appendPQExpBufferStr(&sql, "::pg_catalog.regclass;");
|
|
|
|
executeCommand(conn, "RESET search_path;", echo);
|
|
|
|
/*
|
|
* One row is a typical result, as is a nonexistent relation ERROR.
|
|
* regclassin() unconditionally accepts all-digits input as an OID; if no
|
|
* relation has that OID; this query returns no rows. Catalog corruption
|
|
* might elicit other row counts.
|
|
*/
|
|
res = executeQuery(conn, sql.data, echo);
|
|
ntups = PQntuples(res);
|
|
if (ntups != 1)
|
|
{
|
|
pg_log_error(ngettext("query returned %d row instead of one: %s",
|
|
"query returned %d rows instead of one: %s",
|
|
ntups),
|
|
ntups, sql.data);
|
|
PQfinish(conn);
|
|
exit(1);
|
|
}
|
|
appendPQExpBufferStr(buf,
|
|
fmtQualifiedId(PQgetvalue(res, 0, 1),
|
|
PQgetvalue(res, 0, 0)));
|
|
appendPQExpBufferStr(buf, columns);
|
|
PQclear(res);
|
|
termPQExpBuffer(&sql);
|
|
pg_free(table);
|
|
|
|
PQclear(executeQuery(conn, ALWAYS_SECURE_SEARCH_PATH_SQL, echo));
|
|
}
|
|
|
|
|
|
/*
|
|
* Check yes/no answer in a localized way. 1=yes, 0=no, -1=neither.
|
|
*/
|
|
|
|
/* translator: abbreviation for "yes" */
|
|
#define PG_YESLETTER gettext_noop("y")
|
|
/* translator: abbreviation for "no" */
|
|
#define PG_NOLETTER gettext_noop("n")
|
|
|
|
bool
|
|
yesno_prompt(const char *question)
|
|
{
|
|
char prompt[256];
|
|
|
|
/*------
|
|
translator: This is a question followed by the translated options for
|
|
"yes" and "no". */
|
|
snprintf(prompt, sizeof(prompt), _("%s (%s/%s) "),
|
|
_(question), _(PG_YESLETTER), _(PG_NOLETTER));
|
|
|
|
for (;;)
|
|
{
|
|
char *resp;
|
|
|
|
resp = simple_prompt(prompt, true);
|
|
|
|
if (strcmp(resp, _(PG_YESLETTER)) == 0)
|
|
{
|
|
free(resp);
|
|
return true;
|
|
}
|
|
if (strcmp(resp, _(PG_NOLETTER)) == 0)
|
|
{
|
|
free(resp);
|
|
return false;
|
|
}
|
|
free(resp);
|
|
|
|
printf(_("Please answer \"%s\" or \"%s\".\n"),
|
|
_(PG_YESLETTER), _(PG_NOLETTER));
|
|
}
|
|
}
|