#include "getopt_long.h"
#include "libpq-fe.h"
#include "mb/pg_wchar.h"
+#include "utils/memdebug.h"
typedef struct pe_test_config
*/
bool supports_only_ascii_overlap;
+ /*
+ * Does the escape function have a length input?
+ */
+ bool supports_input_length;
+
bool (*escape) (PGconn *conn, PQExpBuffer target,
const char *unescaped, size_t unescaped_len,
PQExpBuffer escape_err);
{
.name = "PQescapeLiteral",
.reports_errors = true,
+ .supports_input_length = true,
.escape = escape_literal,
},
{
.name = "PQescapeIdentifier",
.reports_errors = true,
+ .supports_input_length = true,
.escape = escape_identifier
},
{
.name = "PQescapeStringConn",
.reports_errors = true,
+ .supports_input_length = true,
.escape = escape_string_conn
},
{
.name = "PQescapeString",
.reports_errors = false,
+ .supports_input_length = true,
.escape = escape_string
},
{
.reports_errors = false,
.supports_only_valid = true,
.supports_only_ascii_overlap = true,
+ .supports_input_length = true,
.escape = escape_replace
},
{
#define TV(enc, string) {.client_encoding = (enc), .escape=string, .escape_len=sizeof(string) - 1, }
+#define TV_LEN(enc, string, len) {.client_encoding = (enc), .escape=string, .escape_len=len, }
static pe_test_vector pe_test_vectors[] =
{
/* expected to work sanity checks */
TV("mule_internal", "\\\x9c';\0;"),
TV("sql_ascii", "1\xC0'"),
+
+ /*
+ * Testcases that are not null terminated for the specified input length.
+ * That's interesting to verify that escape functions don't read beyond
+ * the intended input length.
+ */
+ TV_LEN("gbk", "\x80", 1),
+ TV_LEN("UTF-8", "\xC3\xb6 ", 1),
+ TV_LEN("UTF-8", "\xC3\xb6 ", 2),
};
{
PQExpBuffer testname;
PQExpBuffer details;
+ PQExpBuffer raw_buf;
PQExpBuffer escape_buf;
PQExpBuffer escape_err;
size_t input_encoding_validlen;
escape_err = createPQExpBuffer();
testname = createPQExpBuffer();
details = createPQExpBuffer();
+ raw_buf = createPQExpBuffer();
escape_buf = createPQExpBuffer();
if (ef->supports_only_ascii_overlap &&
input_encoding0_validlen = pg_encoding_verifymbstr(PQclientEncoding(tc->conn),
tv->escape,
- strlen(tv->escape));
- input_encoding0_valid = input_encoding0_validlen == strlen(tv->escape);
+ strnlen(tv->escape, tv->escape_len));
+ input_encoding0_valid = input_encoding0_validlen == strnlen(tv->escape, tv->escape_len);
appendPQExpBuffer(details, "#\t input encoding valid till 0: %d\n",
input_encoding0_valid);
goto out;
+ /*
+ * Put the to-be-escaped data into a buffer, so that we
+ *
+ * a) can mark memory beyond end of the string as inaccessible when using
+ * valgrind
+ *
+ * b) can append extra data beyond the length passed to the escape
+ * function, to verify that that data is not processed.
+ *
+ * TODO: Should we instead/additionally escape twice, once with unmodified
+ * and once with appended input? That way we could compare the two.
+ */
+ appendBinaryPQExpBuffer(raw_buf, tv->escape, tv->escape_len);
+
+#define NEVER_ACCESS_STR "\xff never-to-be-touched"
+ if (ef->supports_input_length)
+ {
+ /*
+ * Append likely invalid string that does *not* contain a null byte
+ * (which'd prevent some invalid accesses to later memory).
+ */
+ appendPQExpBufferStr(raw_buf, NEVER_ACCESS_STR);
+
+ VALGRIND_MAKE_MEM_NOACCESS(&raw_buf->data[tv->escape_len],
+ raw_buf->len - tv->escape_len);
+ }
+ else
+ {
+ /* append invalid string, after \0 */
+ appendPQExpBufferChar(raw_buf, 0);
+ appendPQExpBufferStr(raw_buf, NEVER_ACCESS_STR);
+
+ VALGRIND_MAKE_MEM_NOACCESS(&raw_buf->data[tv->escape_len + 1],
+ raw_buf->len - tv->escape_len - 1);
+ }
+
/* call the to-be-tested escape function */
escape_success = ef->escape(tc->conn, escape_buf,
- tv->escape, tv->escape_len,
+ raw_buf->data, tv->escape_len,
escape_err);
if (!escape_success)
{
if (escape_buf->len > 0)
{
+ bool contains_never;
+
appendPQExpBuffer(details, "#\t escaped string: %zd bytes: ", escape_buf->len);
escapify(details, escape_buf->data, escape_buf->len);
appendPQExpBufferChar(details, '\n');
appendPQExpBuffer(details, "#\t escape encoding valid: %d\n",
escape_encoding_valid);
+
+ /*
+ * Verify that no data beyond the end of the input is included in the
+ * escaped string. It'd be better to use something like memmem()
+ * here, but that's not available everywhere.
+ */
+ contains_never = strstr(escape_buf->data, NEVER_ACCESS_STR) == NULL;
+ report_result(tc, contains_never, testname, details,
+ "escaped data beyond end of input",
+ contains_never ? "no" : "all secrets revealed");
}
else
{
destroyPQExpBuffer(details);
destroyPQExpBuffer(testname);
destroyPQExpBuffer(escape_buf);
+ destroyPQExpBuffer(raw_buf);
}
static void