mirror of
https://git.postgresql.org/git/postgresql.git
synced 2025-01-06 15:24:56 +08:00
Detect bad input for types xid, xid8, and cid.
Historically these input functions just called strtoul or strtoull
and returned the result, with no error detection whatever. Upgrade
them to reject garbage input and out-of-range values, similarly to
our other numeric input routines.
To share the code for this with type oid, adjust the existing
"oidin_subr" to be agnostic about the SQL name of the type it is
handling, and move it to numutils.c; then clone it for 64-bit types.
Because the xid types previously accepted hex and octal input by
reason of calling strtoul[l] with third argument zero, I made the
common subroutine do that too, with the consequence that type oid
now also accepts hex and octal input. In view of 6fcda9aba
, that
seems like a good thing.
While at it, simplify the existing over-complicated handling of
syntax errors from strtoul: we only need one ereturn not three.
Discussion: https://postgr.es/m/3526121.1672000729@sss.pgh.pa.us
This commit is contained in:
parent
63c844a0a5
commit
eb8312a22a
@ -477,6 +477,156 @@ invalid_syntax:
|
||||
"bigint", s)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert input string to an unsigned 32 bit integer.
|
||||
*
|
||||
* Allows any number of leading or trailing whitespace characters.
|
||||
*
|
||||
* If endloc isn't NULL, store a pointer to the rest of the string there,
|
||||
* so that caller can parse the rest. Otherwise, it's an error if anything
|
||||
* but whitespace follows.
|
||||
*
|
||||
* typname is what is reported in error messges.
|
||||
*
|
||||
* If escontext points to an ErrorSaveContext node, that is filled instead
|
||||
* of throwing an error; the caller must check SOFT_ERROR_OCCURRED()
|
||||
* to detect errors.
|
||||
*/
|
||||
uint32
|
||||
uint32in_subr(const char *s, char **endloc,
|
||||
const char *typname, Node *escontext)
|
||||
{
|
||||
uint32 result;
|
||||
unsigned long cvt;
|
||||
char *endptr;
|
||||
|
||||
errno = 0;
|
||||
cvt = strtoul(s, &endptr, 0);
|
||||
|
||||
/*
|
||||
* strtoul() normally only sets ERANGE. On some systems it may also set
|
||||
* EINVAL, which simply means it couldn't parse the input string. Be sure
|
||||
* to report that the same way as the standard error indication (that
|
||||
* endptr == s).
|
||||
*/
|
||||
if ((errno && errno != ERANGE) || endptr == s)
|
||||
ereturn(escontext, 0,
|
||||
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||
errmsg("invalid input syntax for type %s: \"%s\"",
|
||||
typname, s)));
|
||||
|
||||
if (errno == ERANGE)
|
||||
ereturn(escontext, 0,
|
||||
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
||||
errmsg("value \"%s\" is out of range for type %s",
|
||||
s, typname)));
|
||||
|
||||
if (endloc)
|
||||
{
|
||||
/* caller wants to deal with rest of string */
|
||||
*endloc = endptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* allow only whitespace after number */
|
||||
while (*endptr && isspace((unsigned char) *endptr))
|
||||
endptr++;
|
||||
if (*endptr)
|
||||
ereturn(escontext, 0,
|
||||
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||
errmsg("invalid input syntax for type %s: \"%s\"",
|
||||
typname, s)));
|
||||
}
|
||||
|
||||
result = (uint32) cvt;
|
||||
|
||||
/*
|
||||
* Cope with possibility that unsigned long is wider than uint32, in which
|
||||
* case strtoul will not raise an error for some values that are out of
|
||||
* the range of uint32.
|
||||
*
|
||||
* For backwards compatibility, we want to accept inputs that are given
|
||||
* with a minus sign, so allow the input value if it matches after either
|
||||
* signed or unsigned extension to long.
|
||||
*
|
||||
* To ensure consistent results on 32-bit and 64-bit platforms, make sure
|
||||
* the error message is the same as if strtoul() had returned ERANGE.
|
||||
*/
|
||||
#if PG_UINT32_MAX != ULONG_MAX
|
||||
if (cvt != (unsigned long) result &&
|
||||
cvt != (unsigned long) ((int) result))
|
||||
ereturn(escontext, 0,
|
||||
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
||||
errmsg("value \"%s\" is out of range for type %s",
|
||||
s, typname)));
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert input string to an unsigned 64 bit integer.
|
||||
*
|
||||
* Allows any number of leading or trailing whitespace characters.
|
||||
*
|
||||
* If endloc isn't NULL, store a pointer to the rest of the string there,
|
||||
* so that caller can parse the rest. Otherwise, it's an error if anything
|
||||
* but whitespace follows.
|
||||
*
|
||||
* typname is what is reported in error messges.
|
||||
*
|
||||
* If escontext points to an ErrorSaveContext node, that is filled instead
|
||||
* of throwing an error; the caller must check SOFT_ERROR_OCCURRED()
|
||||
* to detect errors.
|
||||
*/
|
||||
uint64
|
||||
uint64in_subr(const char *s, char **endloc,
|
||||
const char *typname, Node *escontext)
|
||||
{
|
||||
uint64 result;
|
||||
char *endptr;
|
||||
|
||||
errno = 0;
|
||||
result = strtou64(s, &endptr, 0);
|
||||
|
||||
/*
|
||||
* strtoul[l] normally only sets ERANGE. On some systems it may also set
|
||||
* EINVAL, which simply means it couldn't parse the input string. Be sure
|
||||
* to report that the same way as the standard error indication (that
|
||||
* endptr == s).
|
||||
*/
|
||||
if ((errno && errno != ERANGE) || endptr == s)
|
||||
ereturn(escontext, 0,
|
||||
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||
errmsg("invalid input syntax for type %s: \"%s\"",
|
||||
typname, s)));
|
||||
|
||||
if (errno == ERANGE)
|
||||
ereturn(escontext, 0,
|
||||
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
||||
errmsg("value \"%s\" is out of range for type %s",
|
||||
s, typname)));
|
||||
|
||||
if (endloc)
|
||||
{
|
||||
/* caller wants to deal with rest of string */
|
||||
*endloc = endptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* allow only whitespace after number */
|
||||
while (*endptr && isspace((unsigned char) *endptr))
|
||||
endptr++;
|
||||
if (*endptr)
|
||||
ereturn(escontext, 0,
|
||||
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||
errmsg("invalid input syntax for type %s: \"%s\"",
|
||||
typname, s)));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* pg_itoa: converts a signed 16-bit integer to its string representation
|
||||
* and returns strlen(a).
|
||||
|
@ -32,106 +32,13 @@
|
||||
* USER I/O ROUTINES *
|
||||
*****************************************************************************/
|
||||
|
||||
/*
|
||||
* Parse a single OID and return its value.
|
||||
*
|
||||
* If endloc isn't NULL, store a pointer to the rest of the string there,
|
||||
* so that caller can parse the rest. Otherwise, it's an error if anything
|
||||
* but whitespace follows.
|
||||
*
|
||||
* If escontext points to an ErrorSaveContext node, that is filled instead
|
||||
* of throwing an error; the caller must check SOFT_ERROR_OCCURRED()
|
||||
* to detect errors.
|
||||
*/
|
||||
static Oid
|
||||
oidin_subr(const char *s, char **endloc, Node *escontext)
|
||||
{
|
||||
unsigned long cvt;
|
||||
char *endptr;
|
||||
Oid result;
|
||||
|
||||
if (*s == '\0')
|
||||
ereturn(escontext, InvalidOid,
|
||||
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||
errmsg("invalid input syntax for type %s: \"%s\"",
|
||||
"oid", s)));
|
||||
|
||||
errno = 0;
|
||||
cvt = strtoul(s, &endptr, 10);
|
||||
|
||||
/*
|
||||
* strtoul() normally only sets ERANGE. On some systems it also may set
|
||||
* EINVAL, which simply means it couldn't parse the input string. This is
|
||||
* handled by the second "if" consistent across platforms.
|
||||
*/
|
||||
if (errno && errno != ERANGE && errno != EINVAL)
|
||||
ereturn(escontext, InvalidOid,
|
||||
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||
errmsg("invalid input syntax for type %s: \"%s\"",
|
||||
"oid", s)));
|
||||
|
||||
if (endptr == s && *s != '\0')
|
||||
ereturn(escontext, InvalidOid,
|
||||
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||
errmsg("invalid input syntax for type %s: \"%s\"",
|
||||
"oid", s)));
|
||||
|
||||
if (errno == ERANGE)
|
||||
ereturn(escontext, InvalidOid,
|
||||
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
||||
errmsg("value \"%s\" is out of range for type %s",
|
||||
s, "oid")));
|
||||
|
||||
if (endloc)
|
||||
{
|
||||
/* caller wants to deal with rest of string */
|
||||
*endloc = endptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* allow only whitespace after number */
|
||||
while (*endptr && isspace((unsigned char) *endptr))
|
||||
endptr++;
|
||||
if (*endptr)
|
||||
ereturn(escontext, InvalidOid,
|
||||
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||
errmsg("invalid input syntax for type %s: \"%s\"",
|
||||
"oid", s)));
|
||||
}
|
||||
|
||||
result = (Oid) cvt;
|
||||
|
||||
/*
|
||||
* Cope with possibility that unsigned long is wider than Oid, in which
|
||||
* case strtoul will not raise an error for some values that are out of
|
||||
* the range of Oid.
|
||||
*
|
||||
* For backwards compatibility, we want to accept inputs that are given
|
||||
* with a minus sign, so allow the input value if it matches after either
|
||||
* signed or unsigned extension to long.
|
||||
*
|
||||
* To ensure consistent results on 32-bit and 64-bit platforms, make sure
|
||||
* the error message is the same as if strtoul() had returned ERANGE.
|
||||
*/
|
||||
#if OID_MAX != ULONG_MAX
|
||||
if (cvt != (unsigned long) result &&
|
||||
cvt != (unsigned long) ((int) result))
|
||||
ereturn(escontext, InvalidOid,
|
||||
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
||||
errmsg("value \"%s\" is out of range for type %s",
|
||||
s, "oid")));
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Datum
|
||||
oidin(PG_FUNCTION_ARGS)
|
||||
{
|
||||
char *s = PG_GETARG_CSTRING(0);
|
||||
Oid result;
|
||||
|
||||
result = oidin_subr(s, NULL, fcinfo->context);
|
||||
result = uint32in_subr(s, NULL, "oid", fcinfo->context);
|
||||
PG_RETURN_OID(result);
|
||||
}
|
||||
|
||||
@ -218,7 +125,8 @@ oidvectorin(PG_FUNCTION_ARGS)
|
||||
oidString++;
|
||||
if (*oidString == '\0')
|
||||
break;
|
||||
result->values[n] = oidin_subr(oidString, &oidString, escontext);
|
||||
result->values[n] = uint32in_subr(oidString, &oidString,
|
||||
"oid", escontext);
|
||||
if (SOFT_ERROR_OCCURRED(escontext))
|
||||
PG_RETURN_NULL();
|
||||
}
|
||||
@ -339,7 +247,8 @@ oidparse(Node *node)
|
||||
* constants by the lexer. Accept these if they are valid OID
|
||||
* strings.
|
||||
*/
|
||||
return oidin_subr(castNode(Float, node)->fval, NULL, NULL);
|
||||
return uint32in_subr(castNode(Float, node)->fval, NULL,
|
||||
"oid", NULL);
|
||||
default:
|
||||
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
|
||||
}
|
||||
|
@ -31,8 +31,10 @@ Datum
|
||||
xidin(PG_FUNCTION_ARGS)
|
||||
{
|
||||
char *str = PG_GETARG_CSTRING(0);
|
||||
TransactionId result;
|
||||
|
||||
PG_RETURN_TRANSACTIONID((TransactionId) strtoul(str, NULL, 0));
|
||||
result = uint32in_subr(str, NULL, "xid", fcinfo->context);
|
||||
PG_RETURN_TRANSACTIONID(result);
|
||||
}
|
||||
|
||||
Datum
|
||||
@ -183,8 +185,10 @@ Datum
|
||||
xid8in(PG_FUNCTION_ARGS)
|
||||
{
|
||||
char *str = PG_GETARG_CSTRING(0);
|
||||
uint64 result;
|
||||
|
||||
PG_RETURN_FULLTRANSACTIONID(FullTransactionIdFromU64(strtou64(str, NULL, 0)));
|
||||
result = uint64in_subr(str, NULL, "xid8", fcinfo->context);
|
||||
PG_RETURN_FULLTRANSACTIONID(FullTransactionIdFromU64(result));
|
||||
}
|
||||
|
||||
Datum
|
||||
@ -321,8 +325,10 @@ Datum
|
||||
cidin(PG_FUNCTION_ARGS)
|
||||
{
|
||||
char *str = PG_GETARG_CSTRING(0);
|
||||
CommandId result;
|
||||
|
||||
PG_RETURN_COMMANDID((CommandId) strtoul(str, NULL, 0));
|
||||
result = uint32in_subr(str, NULL, "cid", fcinfo->context);
|
||||
PG_RETURN_COMMANDID(result);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -51,6 +51,10 @@ extern int32 pg_strtoint32(const char *s);
|
||||
extern int32 pg_strtoint32_safe(const char *s, Node *escontext);
|
||||
extern int64 pg_strtoint64(const char *s);
|
||||
extern int64 pg_strtoint64_safe(const char *s, Node *escontext);
|
||||
extern uint32 uint32in_subr(const char *s, char **endloc,
|
||||
const char *typname, Node *escontext);
|
||||
extern uint64 uint64in_subr(const char *s, char **endloc,
|
||||
const char *typname, Node *escontext);
|
||||
extern int pg_itoa(int16 i, char *a);
|
||||
extern int pg_ultoa_n(uint32 value, char *a);
|
||||
extern int pg_ulltoa_n(uint64 value, char *a);
|
||||
|
@ -13,29 +13,58 @@ select '010'::xid,
|
||||
8 | 42 | 4294967295 | 4294967295 | 8 | 42 | 18446744073709551615 | 18446744073709551615
|
||||
(1 row)
|
||||
|
||||
-- garbage values are not yet rejected (perhaps they should be)
|
||||
-- garbage values
|
||||
select ''::xid;
|
||||
xid
|
||||
-----
|
||||
0
|
||||
(1 row)
|
||||
|
||||
ERROR: invalid input syntax for type xid: ""
|
||||
LINE 1: select ''::xid;
|
||||
^
|
||||
select 'asdf'::xid;
|
||||
xid
|
||||
-----
|
||||
0
|
||||
(1 row)
|
||||
|
||||
ERROR: invalid input syntax for type xid: "asdf"
|
||||
LINE 1: select 'asdf'::xid;
|
||||
^
|
||||
select ''::xid8;
|
||||
xid8
|
||||
------
|
||||
0
|
||||
ERROR: invalid input syntax for type xid8: ""
|
||||
LINE 1: select ''::xid8;
|
||||
^
|
||||
select 'asdf'::xid8;
|
||||
ERROR: invalid input syntax for type xid8: "asdf"
|
||||
LINE 1: select 'asdf'::xid8;
|
||||
^
|
||||
-- Also try it with non-error-throwing API
|
||||
SELECT pg_input_is_valid('42', 'xid');
|
||||
pg_input_is_valid
|
||||
-------------------
|
||||
t
|
||||
(1 row)
|
||||
|
||||
select 'asdf'::xid8;
|
||||
xid8
|
||||
------
|
||||
0
|
||||
SELECT pg_input_is_valid('asdf', 'xid');
|
||||
pg_input_is_valid
|
||||
-------------------
|
||||
f
|
||||
(1 row)
|
||||
|
||||
SELECT pg_input_error_message('0xffffffffff', 'xid');
|
||||
pg_input_error_message
|
||||
---------------------------------------------------
|
||||
value "0xffffffffff" is out of range for type xid
|
||||
(1 row)
|
||||
|
||||
SELECT pg_input_is_valid('42', 'xid8');
|
||||
pg_input_is_valid
|
||||
-------------------
|
||||
t
|
||||
(1 row)
|
||||
|
||||
SELECT pg_input_is_valid('asdf', 'xid8');
|
||||
pg_input_is_valid
|
||||
-------------------
|
||||
f
|
||||
(1 row)
|
||||
|
||||
SELECT pg_input_error_message('0xffffffffffffffffffff', 'xid8');
|
||||
pg_input_error_message
|
||||
--------------------------------------------------------------
|
||||
value "0xffffffffffffffffffff" is out of range for type xid8
|
||||
(1 row)
|
||||
|
||||
-- equality
|
||||
|
@ -10,12 +10,20 @@ select '010'::xid,
|
||||
'0xffffffffffffffff'::xid8,
|
||||
'-1'::xid8;
|
||||
|
||||
-- garbage values are not yet rejected (perhaps they should be)
|
||||
-- garbage values
|
||||
select ''::xid;
|
||||
select 'asdf'::xid;
|
||||
select ''::xid8;
|
||||
select 'asdf'::xid8;
|
||||
|
||||
-- Also try it with non-error-throwing API
|
||||
SELECT pg_input_is_valid('42', 'xid');
|
||||
SELECT pg_input_is_valid('asdf', 'xid');
|
||||
SELECT pg_input_error_message('0xffffffffff', 'xid');
|
||||
SELECT pg_input_is_valid('42', 'xid8');
|
||||
SELECT pg_input_is_valid('asdf', 'xid8');
|
||||
SELECT pg_input_error_message('0xffffffffffffffffffff', 'xid8');
|
||||
|
||||
-- equality
|
||||
select '1'::xid = '1'::xid;
|
||||
select '1'::xid != '1'::xid;
|
||||
|
Loading…
Reference in New Issue
Block a user