21
21
typedef struct
22
22
{
23
23
PLpgSQL_checkstate * cstate ;
24
+ PLpgSQL_expr * expr ;
24
25
char * query_str ;
25
26
} check_funcexpr_walker_params ;
26
27
@@ -29,7 +30,8 @@ static int check_fmt_string(const char *fmt,
29
30
int location ,
30
31
check_funcexpr_walker_params * wp ,
31
32
bool * is_error ,
32
- int * unsafe_expr_location );
33
+ int * unsafe_expr_location ,
34
+ bool no_error );
33
35
34
36
/*
35
37
* Send to ouput all not yet displayed relations and functions.
@@ -223,7 +225,7 @@ check_funcexpr_walker(Node *node, void *context)
223
225
224
226
wp = (check_funcexpr_walker_params * ) context ;
225
227
226
- required_nargs = check_fmt_string (fmt , fexpr -> args , c -> location , wp , & is_error , NULL );
228
+ required_nargs = check_fmt_string (fmt , fexpr -> args , c -> location , wp , & is_error , NULL , false );
227
229
if (!is_error && required_nargs != -1 )
228
230
{
229
231
if (required_nargs + 1 != list_length (fexpr -> args ))
@@ -251,6 +253,7 @@ plpgsql_check_funcexpr(PLpgSQL_checkstate *cstate, Query *query, char *query_str
251
253
252
254
wp .cstate = cstate ;
253
255
wp .query_str = query_str ;
256
+ wp .expr = NULL ;
254
257
255
258
check_funcexpr_walker ((Node * ) query , & wp );
256
259
}
@@ -480,15 +483,17 @@ text_format_parse_format(const char *start_ptr,
480
483
}
481
484
482
485
/*
483
- * Returns number of rquired arguments or -1 when we cannot detect this number
486
+ * Returns number of rquired arguments or -1 when we cannot detect this number.
487
+ * When no_error is true, then this function doesn't raise a error or warning.
484
488
*/
485
489
static int
486
490
check_fmt_string (const char * fmt ,
487
491
List * args ,
488
492
int location ,
489
493
check_funcexpr_walker_params * wp ,
490
494
bool * is_error ,
491
- int * unsafe_expr_location )
495
+ int * unsafe_expr_location ,
496
+ bool no_error )
492
497
{
493
498
const char * cp ;
494
499
const char * end_ptr = fmt + strlen (fmt );
@@ -535,7 +540,7 @@ check_fmt_string(const char *fmt,
535
540
appendStringInfo (& sinfo ,
536
541
"unrecognized format() type specifier \"%c\"" , * cp );
537
542
538
- if (wp )
543
+ if (! no_error )
539
544
plpgsql_check_put_error (wp -> cstate ,
540
545
ERRCODE_INVALID_PARAMETER_VALUE , 0 ,
541
546
sinfo .data ,
@@ -578,7 +583,7 @@ check_fmt_string(const char *fmt,
578
583
{
579
584
Node * arg = list_nth (args , argn - 1 );
580
585
581
- if (plpgsql_check_is_sql_injection_vulnerable (arg , unsafe_expr_location ))
586
+ if (plpgsql_check_is_sql_injection_vulnerable (wp -> cstate , wp -> expr , arg , unsafe_expr_location ))
582
587
{
583
588
/* found vulnerability, stop */
584
589
* is_error = false;
@@ -740,10 +745,13 @@ plpgsql_check_qual_has_fishy_cast(PlannedStmt *plannedstmt, Plan *plan, Param **
740
745
741
746
/*
742
747
* Recursive iterate to deep and search extern params with typcategory "S", and check
743
- * if this value is sanitized.
748
+ * if this value is sanitized. Flag is_safe is true, when result is safe.
744
749
*/
745
750
bool
746
- plpgsql_check_is_sql_injection_vulnerable (Node * node , int * location )
751
+ plpgsql_check_is_sql_injection_vulnerable (PLpgSQL_checkstate * cstate ,
752
+ PLpgSQL_expr * expr ,
753
+ Node * node ,
754
+ int * location )
747
755
{
748
756
if (IsA (node , FuncExpr ))
749
757
{
@@ -755,7 +763,7 @@ plpgsql_check_is_sql_injection_vulnerable(Node *node, int *location)
755
763
{
756
764
Node * arg = lfirst (lc );
757
765
758
- if (plpgsql_check_is_sql_injection_vulnerable (arg , location ))
766
+ if (plpgsql_check_is_sql_injection_vulnerable (cstate , expr , arg , location ))
759
767
{
760
768
is_vulnerable = true;
761
769
break ;
@@ -793,10 +801,15 @@ plpgsql_check_is_sql_injection_vulnerable(Node *node, int *location)
793
801
if (c -> consttype == TEXTOID && !c -> constisnull )
794
802
{
795
803
char * fmt = TextDatumGetCString (c -> constvalue );
804
+ check_funcexpr_walker_params wp ;
796
805
bool is_error ;
797
806
807
+ wp .cstate = cstate ;
808
+ wp .expr = expr ;
809
+ wp .query_str = expr -> query ;
810
+
798
811
* location = -1 ;
799
- check_fmt_string (fmt , fexpr -> args , c -> location , NULL , & is_error , location );
812
+ check_fmt_string (fmt , fexpr -> args , c -> location , & wp , & is_error , location , true );
800
813
801
814
/* only in this case, "format" function obviously sanitize parameters */
802
815
if (!is_error )
@@ -826,7 +839,7 @@ plpgsql_check_is_sql_injection_vulnerable(Node *node, int *location)
826
839
{
827
840
Node * arg = lfirst (lc );
828
841
829
- if (plpgsql_check_is_sql_injection_vulnerable (arg , location ))
842
+ if (plpgsql_check_is_sql_injection_vulnerable (cstate , expr , arg , location ))
830
843
{
831
844
is_vulnerable = true;
832
845
break ;
@@ -861,24 +874,48 @@ plpgsql_check_is_sql_injection_vulnerable(Node *node, int *location)
861
874
}
862
875
else if (IsA (node , NamedArgExpr ))
863
876
{
864
- return plpgsql_check_is_sql_injection_vulnerable ((Node * ) ((NamedArgExpr * ) node )-> arg , location );
877
+ return plpgsql_check_is_sql_injection_vulnerable (cstate , expr , (Node * ) ((NamedArgExpr * ) node )-> arg , location );
865
878
}
866
879
else if (IsA (node , RelabelType ))
867
880
{
868
- return plpgsql_check_is_sql_injection_vulnerable ((Node * ) ((RelabelType * ) node )-> arg , location );
881
+ return plpgsql_check_is_sql_injection_vulnerable (cstate , expr , (Node * ) ((RelabelType * ) node )-> arg , location );
869
882
}
870
883
else if (IsA (node , Param ))
871
884
{
872
885
Param * p = (Param * ) node ;
873
886
874
- if (p -> paramkind == PARAM_EXTERN )
887
+ if (p -> paramkind == PARAM_EXTERN || p -> paramkind == PARAM_EXEC )
875
888
{
876
889
bool typispreferred ;
877
890
char typcategory ;
878
891
879
892
get_type_category_preferred (p -> paramtype , & typcategory , & typispreferred );
880
893
if (typcategory == 'S' )
881
894
{
895
+ if (p -> paramkind == PARAM_EXTERN && p -> paramid > 0 && p -> location != -1 )
896
+ {
897
+ int dno = p -> paramid - 1 ;
898
+
899
+ /*
900
+ * When paramid looks well and related datum is variable with same
901
+ * type, then we can check, if this variable has sanitized content
902
+ * already.
903
+ */
904
+ if (expr && bms_is_member (dno , expr -> paramnos ))
905
+ {
906
+ PLpgSQL_var * var = (PLpgSQL_var * ) cstate -> estate -> datums [dno ];
907
+
908
+ if (var -> dtype == PLPGSQL_DTYPE_VAR )
909
+ {
910
+ if (var -> datatype -> typoid == p -> paramtype )
911
+ {
912
+ if (bms_is_member (dno , cstate -> safe_variables ))
913
+ return false;
914
+ }
915
+ }
916
+ }
917
+ }
918
+
882
919
* location = p -> location ;
883
920
return true;
884
921
}
0 commit comments