package TestPerl::hash_attack; # if the rehashing of the keys in the stash happens due to the hash attack, # mod_perl must not fail to find the previously cached stash entry (response # and fixup handlers in this test). Moreover it must not fail to find # that entry on the subsequent requests. # # the hash attack is detected when HV_MAX_LENGTH_BEFORE_SPLIT keys # find themselves in the same hash bucket, in which case starting from # 5.8.2 the hash will rehash all its keys using a random hash seed # (PL_new_hash_seed, set in mod_perl or via PERL_HASH_SEED environment # variable) # # Prior to the attack condition hashes use the PL_hash_seed, which is # always 0. # # only in 5.8.1 hashes always use a non-zero PL_hash_seed (unless set # to 0 via PERL_HASH_SEED environment variable or compiled without # -DUSE_HASH_SEED or -DUSE_HASH_SEED_EXPLICIT use strict; use warnings FATAL => 'all'; use Apache::TestTrace; use Apache2::Const -compile => 'OK'; use Math::BigInt; use constant MASK_U32 => 2**32; use constant HASH_SEED => 0; # 5.8.2: always zero before the rehashing use constant THRESHOLD => 14; #define HV_MAX_LENGTH_BEFORE_SPLIT use constant START => "a"; # create conditions which will trigger a rehash on the current stash # (__PACKAGE__::). Relevant for perl 5.8.2 and higher. sub init { my $r = shift; no strict 'refs'; my @attack_keys = attack(\%{__PACKAGE__ . "::"}) if $] >= 5.008002; # define a new symbol (sub) after the attack has caused a re-hash # check that mod_perl finds that symbol (fixup2) in the stash no warnings 'redefine'; eval qq[sub fixup2 { return Apache2::Const::OK; }]; $r->push_handlers(PerlFixupHandler => \&fixup2); return Apache2::Const::DECLINED; } sub fixup { return Apache2::Const::OK; } sub handler { my $r = shift; $r->print("ok"); return Apache2::Const::OK; } sub attack { my $stash = shift; #require Hash::Util; # avail since 5.8.0 debug "starting attack (it may take a long time!)"; my @keys; # the minimum of bits required to mount the attack on a hash my $min_bits = log(THRESHOLD)/log(2); # if the hash has already been populated with a significant amount # of entries the number of mask bits can be higher my $keys = scalar keys %$stash; my $bits = $keys ? log($keys)/log(2) : 0; $bits = $min_bits if $min_bits > $bits; $bits = int($bits) < $bits ? int($bits) + 1 : int($bits); # need to add 2 bits to cover the internal split cases $bits += 2; my $mask = 2**$bits-1; debug "mask: $mask ($bits)"; my $s = START; my $c = 0; # get 2 keys on top of the THRESHOLD my $h; while (@keys < THRESHOLD+2) { next if exists $stash->{$s}; $h = hash($s); next unless ($h & $mask) == 0; $c++; $stash->{$s}++; debug sprintf "%2d: %5s, %10s, %s", $c, $s, $h, scalar(%$stash); push @keys, $s; debug "The hash collision attack has been successful" if Internals::HvREHASH(%$stash); } continue { $s++; } # this verifies that the attack was mounted successfully. If # HvREHASH is on it is. Otherwise the sequence wasn't successful. die "Failed to mount the hash collision attack" unless Internals::HvREHASH(%$stash); debug "ending attack"; return @keys; } # trying to provide the fastest equivalent of C macro's PERL_HASH in # Perl - the main complication is that the C macro uses U32 integer # (unsigned int), which we can't do it Perl (it can do I32, with 'use # integer'). So we outsmart Perl and take modules 2*32 after each # calculation, emulating overflows that happen in C. sub hash { my $s = shift; my @c = split //, $s; my $u = HASH_SEED; for (@c) { # (A % M) + (B % M) == (A + B) % M # This works because '+' produces a NV, which is big enough to hold # the intermidiate result. We only need the % before any "^" and "&" # to get the result in the range for an I32. # and << doesn't work on NV, so using 1 << 10 $u += ord; $u += $u * (1 << 10); $u %= MASK_U32; $u ^= $u >> 6; } $u += $u << 3; $u %= MASK_U32; $u ^= $u >> 11; $u %= MASK_U32; $u += $u << 15; $u %= MASK_U32; $u; } # a bit slower but simpler version sub hash_original { my $s = shift; my @c = split //, $s; my $u = HASH_SEED; for (@c) { $u += ord; $u %= MASK_U32; $u += $u << 10; $u %= MASK_U32; $u ^= $u >> 6; $u %= MASK_U32; } $u += $u << 3; $u %= MASK_U32; $u ^= $u >> 11; $u %= MASK_U32; $u += $u << 15; $u %= MASK_U32; $u; } 1; __END__ PerlModule TestPerl::hash_attack PerlInitHandler TestPerl::hash_attack::init # call twice to verify an access to the same hash value after the rehash PerlFixupHandler TestPerl::hash_attack::fixup TestPerl::hash_attack::fixup