From f09d77ae89340b5e0965d24139be1b35375334af Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 16 Apr 2026 15:34:44 -0400 Subject: [PATCH] fix: log bind params correctly when SQL contains literal % characters zmDbDo built log messages by s/\?/'%s'/g on the SQL and then passing the result to sprintf with the bind values. Any literal % in the SQL (LIKE '%foo%' patterns, or the disk-percent substitution used by dynamic filters) was interpreted as a sprintf format spec, producing garbage output or an uncaught sprintf error. Replace the two-step approach with a single regex that substitutes bind values directly, so literal % in the SQL is preserved verbatim. Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/ZoneMinder/lib/ZoneMinder/Database.pm | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Database.pm b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm index 40add11f3..cebaa50ef 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Database.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm @@ -249,17 +249,28 @@ sub end_transaction { $d->{AutoCommit} = $ac; } # end sub end_transaction +# Substitute ? placeholders in $sql with the given bind values for logging. +# Does not rely on sprintf, so it is safe for SQL that contains literal % +# characters (e.g. dynamic filter SQL with disk percent substitution or +# LIKE '%foo%' patterns). +sub _sql_with_bind_values { + my ($sql, @vals) = @_; + $sql =~ s{\?}{ + my $v = shift @vals; + defined $v ? "'$v'" : 'NULL'; + }ge; + return $sql; +} + # Basic execution of $dbh->do but with some pretty logging of the sql on error. sub zmDbDo { my $sql = shift; my $rows = $dbh->do($sql, undef, @_); if ( ! defined $rows ) { - $sql =~ s/\?/'%s'/g; - Error(sprintf("Failed $sql : ", @_) . $dbh->errstr()); + Error('Failed '._sql_with_bind_values($sql, @_).' : '.$dbh->errstr()); } elsif ( ZoneMinder::Logger::logLevel() > INFO ) { ($rows) = $rows =~ /^(.*)$/; # de-taint - $sql =~ s/\?/'%s'/g; - Debug(sprintf("Succeeded $sql : $rows rows affected", @_)); + Debug('Succeeded '._sql_with_bind_values($sql, @_)." : $rows rows affected"); } return $rows; }