Fixed VAR_CACHE/VAR_DONT_CACHE values with reasons for DONT.

Added a DEBUG_MEM define to disable optimization and for future enhcement.
Prevented "counting" vars from being cached.
Prevented vars from being cached unless they are marked "available" in phase.
Now use var->value as the cache hash key as a unique value.
Fixed which pools we are using for rule processing.
Updated regression tests for tfns.
Updated regression test script to handle extra APR_POOL_DEBUG output.
See #364.
This commit is contained in:
brectanus
2008-07-30 22:35:52 +00:00
parent 31869670c1
commit c066e8b3c4
7 changed files with 209 additions and 127 deletions

View File

@@ -12,10 +12,10 @@
SecCacheTransformations On "minlen:1,maxlen:0"
# This should cache it
SecRule ARGS "WillNotMatch" "phase:1,t:none,t:removeWhiteSpace,t:lowercase,pass,nolog"
SecRule ARGS_GET "WillNotMatch" "phase:1,t:none,t:removeWhiteSpace,t:lowercase,pass,nolog"
# This should use the cached value
SecRule ARGS:test "foobar" "phase:1,t:none,t:removeWhiteSpace,t:lowercase,deny"
SecRule ARGS_GET:test "foobar" "phase:1,t:none,t:removeWhiteSpace,t:lowercase,deny"
),
match_log => {
debug => [ qr/removeWhiteSpace,lowercase: "foobar" .*cached/, 1 ],
@@ -40,10 +40,10 @@
SecCacheTransformations On "minlen:1,maxlen:0,incremental:off,maxitems:0"
# This should cache it
SecRule ARGS "WillNotMatch" "phase:1,t:none,t:removeWhiteSpace,pass,nolog"
SecRule ARGS_GET "WillNotMatch" "phase:1,t:none,t:removeWhiteSpace,pass,nolog"
# This should use the partially cached value
SecRule ARGS:test "foobar" "phase:1,t:none,t:removeWhiteSpace,t:lowercase,deny"
SecRule ARGS_GET:test "foobar" "phase:1,t:none,t:removeWhiteSpace,t:lowercase,deny"
),
match_log => {
debug => [ qr/removeWhiteSpace: "FooBar" .*partially cached/, 1 ],
@@ -67,10 +67,10 @@
SecCacheTransformations On "minlen:1,maxlen:0"
# This should cache it
SecRule ARGS "WillNotMatch" "phase:1,t:none,t:removeWhiteSpace,t:lowercase,pass,nolog"
SecRule ARGS_GET "WillNotMatch" "phase:1,t:none,t:removeWhiteSpace,t:lowercase,pass,nolog"
# This should use the cached value
SecRule ARGS:test "foobar" "phase:2,t:none,t:removeWhiteSpace,t:lowercase,deny"
SecRule ARGS_GET:test "foobar" "phase:2,t:none,t:removeWhiteSpace,t:lowercase,deny"
),
match_log => {
-debug => [ qr/removeWhiteSpace,lowercase: "foobar" .*cached/, 1 ],
@@ -94,10 +94,10 @@
SecCacheTransformations On "minlen:1,maxlen:0"
# This should cache it
SecRule ARGS "WillNotMatch" "phase:1,t:none,t:removeWhiteSpace,t:lowercase,pass,nolog"
SecRule ARGS_GET "WillNotMatch" "phase:1,t:none,t:removeWhiteSpace,t:lowercase,pass,nolog"
# This should use the cached value
SecRule ARGS:test "foobar" "phase:1,t:none,t:removeWhiteSpace,t:lowercase,deny"
SecRule ARGS_GET:test "foobar" "phase:1,t:none,t:removeWhiteSpace,t:lowercase,deny"
),
match_log => {
debug => [ qr/removeWhiteSpace,lowercase: "foobar" .*cached/, 1 ],
@@ -116,25 +116,32 @@
SecRuleEngine On
SecDebugLog $ENV{DEBUG_LOG}
SecDebugLogLevel 9
SecRequestBodyAccess On
# We need to make this work no matter what the defaults may change to
SecCacheTransformations On "minlen:1,maxlen:0"
# This should cache it
SecRule ARGS "WillNotMatch" "phase:1,t:none,t:removeWhiteSpace,t:lowercase,pass"
SecRule ARGS "WillNotMatch" "phase:2,t:none,t:removeWhiteSpace,t:lowercase,pass"
# This should see cached versions of *both* ARGS
SecRule ARGS:test "firstval" "phase:1,t:none,t:removeWhiteSpace,t:lowercase,deny,chain"
# This should see cached versions of *both* ARGS_GET
SecRule ARGS:test "queryval" "phase:2,t:none,t:removeWhiteSpace,t:lowercase,deny,chain"
SecRule ARGS:test "firstval" "t:none,t:removeWhiteSpace,t:lowercase,chain"
SecRule ARGS:test "secondval" "t:none,t:removeWhiteSpace,t:lowercase"
),
match_log => {
debug => [ qr/removeWhiteSpace,lowercase: "firstval" .*cached.*removeWhiteSpace,lowercase: "secondval" .*cached/s, 1 ],
debug => [ qr/removeWhiteSpace,lowercase: "queryval" .*removeWhiteSpace,lowercase: "firstval" .*cached.*removeWhiteSpace,lowercase: "secondval" .*cached/s, 1 ],
},
match_response => {
status => qr/^403$/,
},
request => new HTTP::Request(
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/index.html?test=First+Val&test=Second+Val",
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/index.html?test=Query+Val",
[
"Content-Type" => "application/x-www-form-urlencoded",
],
# Args
"test=First+Val&test=Second+Val",
),
},
{
@@ -153,8 +160,7 @@
SecResponseBodyLimit 1048576
# We need to make this work no matter what the defaults may change to
# SecCacheTransformations On "minlen:1,maxlen:0"
SecCacheTransformations Off
SecCacheTransformations On "minlen:1,maxlen:0,maxitems:0"
# This should cache it in all phases
SecRule ARGS "WillNotMatch" "phase:1,t:none,t:removeWhiteSpace,t:lowercase,pass,nolog"
@@ -166,7 +172,7 @@
SecRule ARGS "foobar" "phase:4,t:none,t:removeWhiteSpace,t:lowercase,deny"
),
match_log => {
debug => [ qr/Adding request argument \(BODY\): name "test", value "foobar"/, 60 ],
debug => [ qr/Adding request argument \(BODY\): name "test", value "Foo Bar"/, 60 ],
-error => [ qr/segmentation fault/i, 60 ],
},
match_response => {
@@ -178,6 +184,6 @@
"Content-Type" => "application/x-www-form-urlencoded",
],
# 1000 Args
join("&", map { sprintf "arg%08d=0123456789abcdef+0123456789ABCDEF+0123456789abcdef", $_ } (1 .. 1000))."&test=foobar",
join("&", map { sprintf "arg%08d=0123456789abcdef+0123456789ABCDEF+0123456789abcdef", $_ } (1 .. 1000))."&test=Foo+Bar",
),
},

View File

@@ -35,6 +35,7 @@ my $PID_FILE = "$FILES_DIR/httpd.pid";
my $HTTPD = q(@APXS_HTTPD@);
my $PASSED = 0;
my $TOTAL = 0;
my $BUFSIZ = 32768;
my %C = ();
my %FILE = ();
my $UA_NAME = "ModSecurity Regression Tests/1.2.3";
@@ -49,7 +50,7 @@ if ($HTTPD eq "\@APXS_HTTPD\@") {
$SIG{TERM} = $SIG{INT} = \&handle_interrupt;
my %opt;
getopts('A:E:D:C:T:H:a:p:dh', \%opt);
getopts('A:E:D:C:T:H:a:p:dvh', \%opt);
if ($opt{D}) {
$Data::Dumper::Indent = 1;
@@ -73,6 +74,7 @@ Usage: $SCRIPT [options] [file [N]]
-a file Specify Apache httpd binary (default: httpd)
-p port Specify Apache httpd port (default: 8088)
-d Enable debugging.
-v Enable verbose debugging.
-h This help.
EOT
@@ -230,7 +232,6 @@ sub runfile {
$rc = 1;
msg("response $mtype matched: $m");
dbg($resp);
last;
}
elsif (!$neg and !defined $match) {
@@ -245,13 +246,13 @@ sub runfile {
# Run any arbitrary perl tests
if ($rc == 0 and exists $t{test} and defined $t{test}) {
#dbg("Executing perl test(s)...");
dbg("Executing perl test(s)...") if ($opt{v});
$rc = eval { &{$t{test}} };
if (! defined $rc) {
msg("Error running test: $@");
$rc = -1;
}
#dbg("Perl tests returned: $rc");
dbg("Perl tests returned: $rc") if ($opt{v});
}
# Search for all log matches
@@ -263,15 +264,11 @@ sub runfile {
if ($neg and defined $match) {
$rc = 1;
msg("$mtype log matched: $m->[0]");
msg("Log: $FILE{$mtype}{fn}");
dbg(escape("$FILE{$mtype}{buf}"));
last;
}
elsif (!$neg and !defined $match) {
$rc = 1;
msg("$mtype log failed to match: $m->[0]");
msg("Log: $FILE{$mtype}{fn}");
dbg(escape("$FILE{$mtype}{buf}"));
last;
}
}
@@ -287,13 +284,11 @@ sub runfile {
if ($neg and defined $match) {
$rc = 1;
msg("$fn file matched: $m");
dbg(escape("$FILE{$fn}{buf}"));
last;
}
elsif (!$neg and !defined $match) {
$rc = 1;
msg("$fn file failed match: $m");
dbg(escape("$FILE{$fn}{buf}"));
last;
}
}
@@ -308,7 +303,13 @@ sub runfile {
$pass++;
}
else {
dbg("Test config: $conf_fn");
if ($opt{d}) {
dbg("Test Config: $conf_fn");
dbg("Debug Log: $FILE{debug}{fn}");
dbg(escape("$FILE{debug}{buf}")) if ($opt{v});
dbg("Error Log: $FILE{error}{fn}");
dbg(escape("$FILE{error}{buf}")) if ($opt{v});
}
}
msg(sprintf("%s) %s%s: %s%s", $id, $t{type}, (exists($t{comment}) ? " - $t{comment}" : ""), ($rc ? "failed" : "passed"), ((defined($out) && $out ne "")? " ($out)" : "")));
@@ -352,7 +353,7 @@ sub do_raw_request {
# Join togeather the request
my $r = join("", @_);
dbg($r);
dbg($r) if ($opt{v});
# Write to socket
print $sock "$r";
@@ -375,15 +376,13 @@ sub do_request {
}
if (ref $r eq "HTTP::Request") {
# dbg("REQUEST: ", $r);
my $resp = $UA->request($r);
if ($opt{d}) {
if ($opt{d} and $opt{v}) {
dbg($resp->request()->as_string());
}
return $resp
}
else {
# dbg("REQUEST:\n", $r);
return do_raw_request($r);
}
@@ -409,14 +408,17 @@ sub match_response {
return;
}
sub read_log {
my($name, $timeout, $graph) = @_;
return match_log($name, undef, $timeout, $graph);
}
sub match_log {
my($name, $re, $timeout) = @_;
my($name, $re, $timeout, $graph) = @_;
my $t0 = gettimeofday;
my($fh,$rbuf) = ($FILE{$name}{fd}, \$FILE{$name}{buf});
my $n = length($$rbuf);
msg("Warning: Empty regular expression.") if (!defined $re or $re eq "");
unless (defined $fh) {
msg("Error: File \"$name\" is not opened for matching.");
return;
@@ -424,13 +426,35 @@ sub match_log {
$timeout = 0 unless (defined $timeout);
my $i = 0;
do {
$n += $fh->sysread($$rbuf, 1024, $n);
# dbg("Match \"$re\" in $name \"$$rbuf\" ($n)");
my $nbytes = $fh->sysread($$rbuf, $BUFSIZ, $n);
if (!defined($nbytes)) {
msg("Error: Could not read \"$name\" log: $!");
last;
}
elsif (!defined($re) and $nbytes == 0) {
last;
}
# Remove APR pool debugging
$$rbuf =~ s/POOL DEBUG:[^\n]+PALLOC[^\n]+\n//sg;
$n = length($$rbuf);
#dbg("Match \"$re\" in $name \"$$rbuf\" ($n)");
return $& if ($$rbuf =~ m/$re/m);
# TODO: Use select()/poll()
sleep 0.1;
sleep 0.1 unless ($nbytes == $BUFSIZ);
if ($graph and $opt{v}) {
$i++;
if ($i == 10) {
$i=0;
print STDERR "."
}
}
} while (gettimeofday - $t0 < $timeout);
print STDERR "\n" if ($graph and $opt{v});
return;
}
@@ -524,7 +548,7 @@ sub httpd_start {
my $httpd_out;
my $httpd_pid = open3(undef, $httpd_out, undef, @p) or quit(1);
my $out = join("\\n", split(/\n/, <$httpd_out>));
my $out = join("\\n", grep(!/POOL DEBUG/, (<$httpd_out>)));
close $httpd_out;
waitpid($httpd_pid, 0);
@@ -549,7 +573,7 @@ sub httpd_start {
}
# Look for startup msg
unless (defined match_log("error", qr/resuming normal operations/, 10)) {
unless (defined match_log("error", qr/resuming normal operations/, 60, $opt{d})) {
dbg(join(" ", map { quote_shell($_) } @p));
dbg(match_log("error", qr/(^.*ModSecurity: .*)/sm, 10));
msg("Httpd server failed to start.");
@@ -571,7 +595,7 @@ sub httpd_stop {
my $httpd_out;
my $httpd_pid = open3(undef, $httpd_out, undef, @p) or quit(1);
my $out = join("\\n", split(/\n/, <$httpd_out>));
my $out = join("\\n", grep(!/POOL DEBUG/, (<$httpd_out>)));
close $httpd_out;
waitpid($httpd_pid, 0);
@@ -595,7 +619,7 @@ sub httpd_stop {
}
# Look for startup msg
unless (defined match_log("error", qr/caught SIG[A-Z]+, shutting down/, 10)) {
unless (defined match_log("error", qr/caught SIG[A-Z]+, shutting down/, 60, $opt{d})) {
dbg(join(" ", map { quote_shell($_) } @p));
msg("Httpd server failed to shutdown.");
return -1;
@@ -617,7 +641,7 @@ sub httpd_reload {
my $httpd_out;
my $httpd_pid = open3(undef, $httpd_out, undef, @p) or quit(1);
my $out = join("\\n", split(/\n/, <$httpd_out>));
my $out = join("\\n", grep(!/POOL DEBUG/, (<$httpd_out>)));
close $httpd_out;
waitpid($httpd_pid, 0);
@@ -641,7 +665,7 @@ sub httpd_reload {
}
# Look for startup msg
unless (defined match_log("error", qr/resuming normal operations/, 10)) {
unless (defined match_log("error", qr/resuming normal operations/, 60, $opt{d})) {
dbg(join(" ", map { quote_shell($_) } @p));
msg("Httpd server failed to reload.");
return -1;