17
17
#include "getopt_long.h"
18
18
#include "libpq-fe.h"
19
19
#include "mb/pg_wchar.h"
20
+ #include "utils/memdebug.h"
20
21
21
22
22
23
typedef struct pe_test_config
@@ -56,6 +57,11 @@ typedef struct pe_test_escape_func
56
57
*/
57
58
bool supports_only_ascii_overlap ;
58
59
60
+ /*
61
+ * Does the escape function have a length input?
62
+ */
63
+ bool supports_input_length ;
64
+
59
65
bool (* escape ) (PGconn * conn , PQExpBuffer target ,
60
66
const char * unescaped , size_t unescaped_len ,
61
67
PQExpBuffer escape_err );
@@ -234,28 +240,33 @@ static pe_test_escape_func pe_test_escape_funcs[] =
234
240
{
235
241
.name = "PQescapeLiteral" ,
236
242
.reports_errors = true,
243
+ .supports_input_length = true,
237
244
.escape = escape_literal ,
238
245
},
239
246
{
240
247
.name = "PQescapeIdentifier" ,
241
248
.reports_errors = true,
249
+ .supports_input_length = true,
242
250
.escape = escape_identifier
243
251
},
244
252
{
245
253
.name = "PQescapeStringConn" ,
246
254
.reports_errors = true,
255
+ .supports_input_length = true,
247
256
.escape = escape_string_conn
248
257
},
249
258
{
250
259
.name = "PQescapeString" ,
251
260
.reports_errors = false,
261
+ .supports_input_length = true,
252
262
.escape = escape_string
253
263
},
254
264
{
255
265
.name = "replace" ,
256
266
.reports_errors = false,
257
267
.supports_only_valid = true,
258
268
.supports_only_ascii_overlap = true,
269
+ .supports_input_length = true,
259
270
.escape = escape_replace
260
271
},
261
272
{
@@ -272,6 +283,7 @@ static pe_test_escape_func pe_test_escape_funcs[] =
272
283
273
284
274
285
#define TV (enc , string ) {.client_encoding = (enc), .escape=string, .escape_len=sizeof(string) - 1, }
286
+ #define TV_LEN (enc , string , len ) {.client_encoding = (enc), .escape=string, .escape_len=len, }
275
287
static pe_test_vector pe_test_vectors [] =
276
288
{
277
289
/* expected to work sanity checks */
@@ -359,6 +371,15 @@ static pe_test_vector pe_test_vectors[] =
359
371
TV ("mule_internal" , "\\\x9c';\0;" ),
360
372
361
373
TV ("sql_ascii" , "1\xC0'" ),
374
+
375
+ /*
376
+ * Testcases that are not null terminated for the specified input length.
377
+ * That's interesting to verify that escape functions don't read beyond
378
+ * the intended input length.
379
+ */
380
+ TV_LEN ("gbk" , "\x80" , 1 ),
381
+ TV_LEN ("UTF-8" , "\xC3\xb6 " , 1 ),
382
+ TV_LEN ("UTF-8" , "\xC3\xb6 " , 2 ),
362
383
};
363
384
364
385
@@ -521,6 +542,7 @@ test_one_vector_escape(pe_test_config *tc, const pe_test_vector *tv, const pe_te
521
542
{
522
543
PQExpBuffer testname ;
523
544
PQExpBuffer details ;
545
+ PQExpBuffer raw_buf ;
524
546
PQExpBuffer escape_buf ;
525
547
PQExpBuffer escape_err ;
526
548
size_t input_encoding_validlen ;
@@ -534,6 +556,7 @@ test_one_vector_escape(pe_test_config *tc, const pe_test_vector *tv, const pe_te
534
556
escape_err = createPQExpBuffer ();
535
557
testname = createPQExpBuffer ();
536
558
details = createPQExpBuffer ();
559
+ raw_buf = createPQExpBuffer ();
537
560
escape_buf = createPQExpBuffer ();
538
561
539
562
if (ef -> supports_only_ascii_overlap &&
@@ -567,8 +590,8 @@ test_one_vector_escape(pe_test_config *tc, const pe_test_vector *tv, const pe_te
567
590
568
591
input_encoding0_validlen = pg_encoding_verifymbstr (PQclientEncoding (tc -> conn ),
569
592
tv -> escape ,
570
- strlen (tv -> escape ));
571
- input_encoding0_valid = input_encoding0_validlen == strlen (tv -> escape );
593
+ strnlen (tv -> escape , tv -> escape_len ));
594
+ input_encoding0_valid = input_encoding0_validlen == strnlen (tv -> escape , tv -> escape_len );
572
595
appendPQExpBuffer (details , "#\t input encoding valid till 0: %d\n" ,
573
596
input_encoding0_valid );
574
597
@@ -580,9 +603,45 @@ test_one_vector_escape(pe_test_config *tc, const pe_test_vector *tv, const pe_te
580
603
goto out ;
581
604
582
605
606
+ /*
607
+ * Put the to-be-escaped data into a buffer, so that we
608
+ *
609
+ * a) can mark memory beyond end of the string as inaccessible when using
610
+ * valgrind
611
+ *
612
+ * b) can append extra data beyond the length passed to the escape
613
+ * function, to verify that that data is not processed.
614
+ *
615
+ * TODO: Should we instead/additionally escape twice, once with unmodified
616
+ * and once with appended input? That way we could compare the two.
617
+ */
618
+ appendBinaryPQExpBuffer (raw_buf , tv -> escape , tv -> escape_len );
619
+
620
+ #define NEVER_ACCESS_STR "\xff never-to-be-touched"
621
+ if (ef -> supports_input_length )
622
+ {
623
+ /*
624
+ * Append likely invalid string that does *not* contain a null byte
625
+ * (which'd prevent some invalid accesses to later memory).
626
+ */
627
+ appendPQExpBufferStr (raw_buf , NEVER_ACCESS_STR );
628
+
629
+ VALGRIND_MAKE_MEM_NOACCESS (& raw_buf -> data [tv -> escape_len ],
630
+ raw_buf -> len - tv -> escape_len );
631
+ }
632
+ else
633
+ {
634
+ /* append invalid string, after \0 */
635
+ appendPQExpBufferChar (raw_buf , 0 );
636
+ appendPQExpBufferStr (raw_buf , NEVER_ACCESS_STR );
637
+
638
+ VALGRIND_MAKE_MEM_NOACCESS (& raw_buf -> data [tv -> escape_len + 1 ],
639
+ raw_buf -> len - tv -> escape_len - 1 );
640
+ }
641
+
583
642
/* call the to-be-tested escape function */
584
643
escape_success = ef -> escape (tc -> conn , escape_buf ,
585
- tv -> escape , tv -> escape_len ,
644
+ raw_buf -> data , tv -> escape_len ,
586
645
escape_err );
587
646
if (!escape_success )
588
647
{
@@ -592,6 +651,8 @@ test_one_vector_escape(pe_test_config *tc, const pe_test_vector *tv, const pe_te
592
651
593
652
if (escape_buf -> len > 0 )
594
653
{
654
+ bool contains_never ;
655
+
595
656
appendPQExpBuffer (details , "#\t escaped string: %zd bytes: " , escape_buf -> len );
596
657
escapify (details , escape_buf -> data , escape_buf -> len );
597
658
appendPQExpBufferChar (details , '\n' );
@@ -603,6 +664,16 @@ test_one_vector_escape(pe_test_config *tc, const pe_test_vector *tv, const pe_te
603
664
604
665
appendPQExpBuffer (details , "#\t escape encoding valid: %d\n" ,
605
666
escape_encoding_valid );
667
+
668
+ /*
669
+ * Verify that no data beyond the end of the input is included in the
670
+ * escaped string. It'd be better to use something like memmem()
671
+ * here, but that's not available everywhere.
672
+ */
673
+ contains_never = strstr (escape_buf -> data , NEVER_ACCESS_STR ) == NULL ;
674
+ report_result (tc , contains_never , testname , details ,
675
+ "escaped data beyond end of input" ,
676
+ contains_never ? "no" : "all secrets revealed" );
606
677
}
607
678
else
608
679
{
@@ -693,6 +764,7 @@ test_one_vector_escape(pe_test_config *tc, const pe_test_vector *tv, const pe_te
693
764
destroyPQExpBuffer (details );
694
765
destroyPQExpBuffer (testname );
695
766
destroyPQExpBuffer (escape_buf );
767
+ destroyPQExpBuffer (raw_buf );
696
768
}
697
769
698
770
static void
0 commit comments