From dc2e6f36bdccee6a4a982a04c1c8ab4e8fcdb7bf Mon Sep 17 00:00:00 2001
From: Howard Chu <hyc@openldap.org>
Date: Wed, 23 Mar 2022 12:43:31 +0000
Subject: [PATCH] ITS#9815 slapd-sql: escape filter values

---
 servers/slapd/back-sql/search.c | 123 +++++++++++++++++++++++++++-----
 1 file changed, 105 insertions(+), 18 deletions(-)

diff --git a/servers/slapd/back-sql/search.c b/servers/slapd/back-sql/search.c
index afd3043788..ff2e3f09a0 100644
--- a/servers/slapd/back-sql/search.c
+++ b/servers/slapd/back-sql/search.c
@@ -63,6 +63,38 @@ static void send_paged_response(
 	ID  *lastid );
 #endif /* ! BACKSQL_ARBITRARY_KEY */
 
+/* Look for chars that need to be escaped, return count of them.
+ * If out is non-NULL, copy escape'd val to it.
+ */
+static int
+backsql_val_escape( Operation *op, struct berval *in, struct berval *out )
+{
+	char *ptr, *end;
+	int q = 0;
+
+	ptr = in->bv_val;
+	end = ptr + in->bv_len;
+	while (ptr < end) {
+		if ( *ptr == '\'' )
+			q++;
+		ptr++;
+	}
+	if ( q && out ) {
+		char *dst;
+		out->bv_len = in->bv_len + q;
+		out->bv_val = op->o_tmpalloc( out->bv_len + 1, op->o_tmpmemctx );
+		ptr = in->bv_val;
+		dst = out->bv_val;
+		while (ptr < end ) {
+			if ( *ptr == '\'' )
+				*dst++ = '\'';
+			*dst++ = *ptr++;
+		}
+		*dst = '\0';
+	}
+	return q;
+}
+
 static int
 backsql_attrlist_add( backsql_srch_info *bsi, AttributeDescription *ad )
 {
@@ -429,6 +461,8 @@ backsql_process_sub_filter( backsql_srch_info *bsi, Filter *f,
 	backsql_info		*bi = (backsql_info *)bsi->bsi_op->o_bd->be_private;
 	int			i;
 	int			casefold = 0;
+	int			escaped = 0;
+	struct berval	escval, *fvalue;
 
 	if ( !f ) {
 		return 0;
@@ -462,50 +496,68 @@ backsql_process_sub_filter( backsql_srch_info *bsi, Filter *f,
 
 		BER_BVZERO( &bv );
 		if ( f->f_sub_initial.bv_val ) {
-			bv.bv_len += f->f_sub_initial.bv_len;
+			bv.bv_len += f->f_sub_initial.bv_len + backsql_val_escape( NULL, &f->f_sub_initial, NULL );
 		}
 		if ( f->f_sub_any != NULL ) {
 			for ( a = 0; f->f_sub_any[ a ].bv_val != NULL; a++ ) {
-				bv.bv_len += f->f_sub_any[ a ].bv_len;
+				bv.bv_len += f->f_sub_any[ a ].bv_len + backsql_val_escape( NULL, &f->f_sub_any[ a ], NULL );
 			}
 		}
 		if ( f->f_sub_final.bv_val ) {
-			bv.bv_len += f->f_sub_final.bv_len;
+			bv.bv_len += f->f_sub_final.bv_len + backsql_val_escape( NULL, &f->f_sub_final, NULL );
 		}
 		bv.bv_len = 2 * bv.bv_len - 1;
 		bv.bv_val = ch_malloc( bv.bv_len + 1 );
 
 		s = 0;
 		if ( !BER_BVISNULL( &f->f_sub_initial ) ) {
-			bv.bv_val[ s ] = f->f_sub_initial.bv_val[ 0 ];
-			for ( i = 1; i < f->f_sub_initial.bv_len; i++ ) {
+			fvalue = &f->f_sub_initial;
+			escaped = backsql_val_escape( bsi->bsi_op, fvalue, &escval );
+			if ( escaped )
+				fvalue = &escval;
+			bv.bv_val[ s ] = fvalue->bv_val[ 0 ];
+			for ( i = 1; i < fvalue->bv_len; i++ ) {
 				bv.bv_val[ s + 2 * i - 1 ] = '%';
-				bv.bv_val[ s + 2 * i ] = f->f_sub_initial.bv_val[ i ];
+				bv.bv_val[ s + 2 * i ] = fvalue->bv_val[ i ];
 			}
 			bv.bv_val[ s + 2 * i - 1 ] = '%';
 			s += 2 * i;
+			if ( escaped )
+				bsi->bsi_op->o_tmpfree( escval.bv_val, bsi->bsi_op->o_tmpmemctx );
 		}
 
 		if ( f->f_sub_any != NULL ) {
 			for ( a = 0; !BER_BVISNULL( &f->f_sub_any[ a ] ); a++ ) {
-				bv.bv_val[ s ] = f->f_sub_any[ a ].bv_val[ 0 ];
-				for ( i = 1; i < f->f_sub_any[ a ].bv_len; i++ ) {
+				fvalue = &f->f_sub_any[ a ];
+				escaped = backsql_val_escape( bsi->bsi_op, fvalue, &escval );
+				if ( escaped )
+					fvalue = &escval;
+				bv.bv_val[ s ] = fvalue->bv_val[ 0 ];
+				for ( i = 1; i < fvalue->bv_len; i++ ) {
 					bv.bv_val[ s + 2 * i - 1 ] = '%';
-					bv.bv_val[ s + 2 * i ] = f->f_sub_any[ a ].bv_val[ i ];
+					bv.bv_val[ s + 2 * i ] = fvalue->bv_val[ i ];
 				}
 				bv.bv_val[ s + 2 * i - 1 ] = '%';
 				s += 2 * i;
+				if ( escaped )
+					bsi->bsi_op->o_tmpfree( escval.bv_val, bsi->bsi_op->o_tmpmemctx );
 			}
 		}
 
 		if ( !BER_BVISNULL( &f->f_sub_final ) ) {
-			bv.bv_val[ s ] = f->f_sub_final.bv_val[ 0 ];
-			for ( i = 1; i < f->f_sub_final.bv_len; i++ ) {
+			fvalue = &f->f_sub_final;
+			escaped = backsql_val_escape( bsi->bsi_op, fvalue, &escval );
+			if ( escaped )
+				fvalue = &escval;
+			bv.bv_val[ s ] = fvalue->bv_val[ 0 ];
+			for ( i = 1; i < fvalue->bv_len; i++ ) {
 				bv.bv_val[ s + 2 * i - 1 ] = '%';
-				bv.bv_val[ s + 2 * i ] = f->f_sub_final.bv_val[ i ];
+				bv.bv_val[ s + 2 * i ] = fvalue->bv_val[ i ];
 			}
-				bv.bv_val[ s + 2 * i - 1 ] = '%';
+			bv.bv_val[ s + 2 * i - 1 ] = '%';
 			s += 2 * i;
+			if ( escaped )
+				bsi->bsi_op->o_tmpfree( escval.bv_val, bsi->bsi_op->o_tmpmemctx );
 		}
 
 		bv.bv_val[ s - 1 ] = '\0';
@@ -561,11 +613,17 @@ backsql_process_sub_filter( backsql_srch_info *bsi, Filter *f,
 			f->f_sub_initial.bv_val, 0 );
 #endif /* BACKSQL_TRACE */
 
+		fvalue = &f->f_sub_initial;
+		escaped = backsql_val_escape( bsi->bsi_op, fvalue, &escval );
+		if ( escaped )
+			fvalue = &escval;
 		start = bsi->bsi_flt_where.bb_val.bv_len;
 		backsql_strfcat_x( &bsi->bsi_flt_where,
 				bsi->bsi_op->o_tmpmemctx,
 				"b",
-				&f->f_sub_initial );
+				fvalue );
+		if ( escaped )
+			bsi->bsi_op->o_tmpfree( escval.bv_val, bsi->bsi_op->o_tmpmemctx );
 		if ( casefold && BACKSQL_AT_CANUPPERCASE( at ) ) {
 			ldap_pvt_str2upper( &bsi->bsi_flt_where.bb_val.bv_val[ start ] );
 		}
@@ -586,12 +644,18 @@ backsql_process_sub_filter( backsql_srch_info *bsi, Filter *f,
 				i, f->f_sub_any[ i ].bv_val );
 #endif /* BACKSQL_TRACE */
 
+			fvalue = &f->f_sub_any[ i ];
+			escaped = backsql_val_escape( bsi->bsi_op, fvalue, &escval );
+			if ( escaped )
+				fvalue = &escval;
 			start = bsi->bsi_flt_where.bb_val.bv_len;
 			backsql_strfcat_x( &bsi->bsi_flt_where,
 					bsi->bsi_op->o_tmpmemctx,
 					"bc",
-					&f->f_sub_any[ i ],
+					fvalue,
 					'%' );
+			if ( escaped )
+				bsi->bsi_op->o_tmpfree( escval.bv_val, bsi->bsi_op->o_tmpmemctx );
 			if ( casefold && BACKSQL_AT_CANUPPERCASE( at ) ) {
 				/*
 				 * Note: toupper('%') = '%'
@@ -611,11 +675,17 @@ backsql_process_sub_filter( backsql_srch_info *bsi, Filter *f,
 			f->f_sub_final.bv_val, 0 );
 #endif /* BACKSQL_TRACE */
 
+		fvalue = &f->f_sub_final;
+		escaped = backsql_val_escape( bsi->bsi_op, fvalue, &escval );
+		if ( escaped )
+			fvalue = &escval;
 		start = bsi->bsi_flt_where.bb_val.bv_len;
     		backsql_strfcat_x( &bsi->bsi_flt_where,
 				bsi->bsi_op->o_tmpmemctx,
 				"b",
-				&f->f_sub_final );
+				fvalue );
+		if ( escaped )
+			bsi->bsi_op->o_tmpfree( escval.bv_val, bsi->bsi_op->o_tmpmemctx );
   		if ( casefold && BACKSQL_AT_CANUPPERCASE( at ) ) {
 			ldap_pvt_str2upper( &bsi->bsi_flt_where.bb_val.bv_val[ start ] );
 		}
@@ -1183,6 +1253,8 @@ backsql_process_filter_attr( backsql_srch_info *bsi, Filter *f, backsql_at_map_r
 	struct berval		*filter_value = NULL;
 	MatchingRule		*matching_rule = NULL;
 	struct berval		ordering = BER_BVC("<=");
+	struct berval		escval;
+	int					escaped = 0;
 
 	Debug( LDAP_DEBUG_TRACE, "==>backsql_process_filter_attr(%s)\n",
 		at->bam_ad->ad_cname.bv_val, 0, 0 );
@@ -1237,6 +1309,10 @@ equality_match:;
 			casefold = 1;
 		}
 
+		escaped = backsql_val_escape( bsi->bsi_op, filter_value, &escval );
+		if ( escaped )
+			filter_value = &escval;
+
 		/* FIXME: directoryString filtering should use a similar
 		 * approach to deal with non-prettified values like
 		 * " A  non    prettified   value  ", by using a LIKE
@@ -1317,6 +1393,10 @@ equality_match:;
 			casefold = 1;
 		}
 
+		escaped = backsql_val_escape( bsi->bsi_op, filter_value, &escval );
+		if ( escaped )
+			filter_value = &escval;
+
 		/*
 		 * FIXME: should we uppercase the operands?
 		 */
@@ -1350,7 +1430,7 @@ equality_match:;
 					&at->bam_sel_expr,
 					&ordering,
 					'\'',
-					&f->f_av_value,
+					filter_value,
 					(ber_len_t)STRLENOF( /* (' */ "')" ),
 						/* ( */ "')" );
 		}
@@ -1374,13 +1454,17 @@ equality_match:;
 	case LDAP_FILTER_APPROX:
 		/* we do our best */
 
+		filter_value = &f->f_av_value;
+		escaped = backsql_val_escape( bsi->bsi_op, filter_value, &escval );
+		if ( escaped )
+			filter_value = &escval;
 		/*
 		 * maybe we should check type of at->sel_expr here somehow,
 		 * to know whether upper_func is applicable, but for now
 		 * upper_func stuff is made for Oracle, where UPPER is
 		 * safely applicable to NUMBER etc.
 		 */
-		(void)backsql_process_filter_like( bsi, at, 1, &f->f_av_value );
+		(void)backsql_process_filter_like( bsi, at, 1, filter_value );
 		break;
 
 	default:
@@ -1394,6 +1478,9 @@ equality_match:;
 
 	}
 
+	if ( escaped )
+		bsi->bsi_op->o_tmpfree( escval.bv_val, bsi->bsi_op->o_tmpmemctx );
+
 	Debug( LDAP_DEBUG_TRACE, "<==backsql_process_filter_attr(%s)\n",
 		at->bam_ad->ad_cname.bv_val, 0, 0 );
 
-- 
2.36.0

