This post is part of a series on Mohammad Anwar’s excellent Weekly Challenge, where hackers submit solutions in Perl, Raku, or any other language, to two different challenges every week. (It’s a lot of fun, if you’re into that sort of thing.)
I’ve recently moved all the sites I host to a new server, including ry.ca, and realized it’s been far too long since I wrote a blog post. And, indeed, far too long since I’ve had time for any sort of recreational programming. So let’s fix that!
Task 1: My Keyboard is Broken
This task posits that age old question: which words could I still type if my keyboard was broken? For example, if the o
key on your keyboard is broken, you can (correctly) type help
, but not keyboard
or broken
.
The solution (made possible to code without an o
for your convenience):
sub pssible($str, @brken) {
grep !/[@brken\0]/i, split /\s+/, $str
}
I’m using subroutine signatures, but they’re not necessary:
sub pssible { grep !/[@_\0]/i, split /\s+/, shift }
Otherwise the only slightly weird thing I’m doing is interpolating an array inside a regex. The \0
(NUL
) in the regex is there to prevent an edge case where @brken
is empty. In that case, the regex would be /[]/i
, and Perl doesn’t like having empty square brackets:
$ perl -e '/[]/'
Unmatched [ in regex; marked by <-- HERE in m/[ <-- HERE ]/ at -e line 1.
Perl assumes the ]
is the first character you want to match, but then there is no closing ]
. If you were to instead try perl -e '/[]]/'
, you’d have a valid regex that matches strings containing ]
.
Task 2: Reverse Prefix
This task asks us to take a $str
ing and a $char
as input. Find the $char
, and reverse everything up to (and including!) $char
, but leave the rest alone. Thus, the name is a bit of a misnomer, because we’re reversing more than just the prefix.
An example:
rev_prefix('programming', 'g'); # gorpramming
# PRE CH POST
# pro g ramming
# g orp ramming
And, my solution:
sub rev_prefix { $_[0] =~ s/^(.+?\Q$_[1]\E)/reverse $1/er }
Here I match the beginning of the string (^
), followed by a capturing non-greedy number of arbitrary characters plus the first CH ($_[1]
). I do not match the POST section of the string at all.
Then in the replacement (with /e
active, so everything is executed as a Perl expression), I simply reverse($1)
. Since this is a substitution, the original PRE and CH parts of the string are replaced by their reversed version, and the POST part is left alone. And thanks to /r
, the resulting string is returned, avoiding the need for a temp variable.
Security (\Q
and \E
)
The \Q
and \E
around $_[1]
are necessary if that input can come from a user, file, or other untrusted source, and is not fully validated. I’d argue the \Q
and \E
are pretty cheap insurance even when you think your input is safe. Why is that, exactly? Well, did you know you can run arbitrary code in a regex?
rev_prefix('lol', qr/(?{ die })/)
This will actually execute the code in the { ... }
section, and kill the program. And that’s me being nice!
You might now think Task 1 has a similar vulnerability. So what happens if we try this?
pssible('foo', 'a](?{ die })[');
That would produce a regex like this:
qr/[a](?{ die })[\0]/
However, in this case, Perl refuses compilation:
Eval-group not allowed at runtime, use re 'eval'
in regex m/[a](?{ die })[\0]/ at ...
This may be safe enough for a contrived Perl challenge task, but a crafty attacker can still crash your program, exploit your innocent-looking regex to grind your processor to a halt, or even expose enough program internals to compromise your system. A deep dive would be out of scope for this blog post, but suffice to say, if you’re dealing with user input, validate it!
@chars = grep /^\w$/, @chars;
Tests
I left my tests in the t/
subdirectory, so running prove
from the main ryan-thompson/
directory will run the tests, and they should all pass.
One Reply to “PWC 341 › Brken Keybards and Reverse Prefixes”