Wednesday, August 26, 2015

Using Dispatch Tables To Improve Application Security

Update: I have changed the title to "Using Dispatch Tables To Improve Application Security" for clarity.

At a previous job, I saw some code that asked the user which function they wanted to run and then executed a subroutine with that name. This code demonstrates why such a practice is bad:

  1. use strict;
  2. use warnings;
  3. sub greet { print "Hello!\n" }
  4. sub inquire { print "How are you?\n" }
  5. sub bye { print "Farewell!\n" }
  6. sub delete_all_files { print "*KABOOM*\n" }
  7. sub insecure_call {
  8. no strict 'refs';
  9. shift->();
  10. }
  11. insecure_call('greet');
  12. insecure_call('inquire');
  13. insecure_call('bye');
  14. insecure_call('delete_all_files');

Output:

Hello!
How are you?
Farewell!
*KABOOM*

One solution to this is the dispatch table. With a dispatch table, you define up front which calls are legal for an outsider to make:

  1. use strict;
  2. use warnings;
  3. my %dispatch = (
  4. greet => \&greet,
  5. inquire => \&inquire,
  6. bye => \&bye,
  7. );
  8. sub greet { print "Hello!\n" }
  9. sub inquire { print "How are you?\n" }
  10. sub bye { print "Farewell!\n" }
  11. sub delete_all_files { print "*KABOOM*\n" }
  12. sub secure_call {
  13. my $call = shift;
  14. if ( ref $dispatch{$call} eq 'CODE' ) {
  15. $dispatch{$call}->();
  16. }
  17. else {
  18. warn "Invalid call $call";
  19. }
  20. }
  21. secure_call('greet');
  22. secure_call('inquire');
  23. secure_call('bye');
  24. secure_call('delete_all_files');

Output:

Hello!
How are you?
Farewell!
Invalid call delete_all_files at example_2a line 22.

The thing that bugs me about this particular solution (and I'll admit it's minor) is the repetition:

  1. my %dispatch = (
  2. greet => \&greet,
  3. inquire => \&inquire,
  4. bye => \&bye,
  5. );

To me, this reads like:

To go to greet, type 'greet'.
To go to inquire, type 'inquire'.
To go to bye, type 'bye'.

When it could just be asking "Which function do you wish to use?"

So, we could build the dispatch table dynamically from a list of acceptable calls:

  1. use strict;
  2. use warnings;
  3. my %dispatch;
  4. my @valid_calls = qw( greet inquire bye );
  5. sub greet { print "Hello!\n" }
  6. sub inquire { print "How are you?\n" }
  7. sub bye { print "Farewell!\n" }
  8. sub delete_all_files { print "*KABOOM*\n" }
  9. sub build_dispatch_table {
  10. no strict 'refs';
  11. %dispatch = map { $_ => *{$_} } @valid_calls;
  12. }
  13. sub secure_call {
  14. my $call = shift;
  15. if ( $dispatch{$call} ) {
  16. $dispatch{$call}->();
  17. }
  18. else {
  19. warn "Invalid call $call\n";
  20. }
  21. }
  22. build_dispatch_table();
  23. secure_call('greet');
  24. secure_call('inquire');
  25. secure_call('bye');
  26. secure_call('delete_all_files');
  27. print "\nBut, now this works because of the typeglob *{}\n";
  28. our @greet = qw( This is an array );
  29. print "@{$dispatch{greet}}\n";
  30. print "which annoys me even though it's probably inconsequential\n";

Output:

Hello!
How are you?
Farewell!
Invalid call delete_all_files

But, now this works because of the typeglob *{}
This is an array
which annoys me even though it's probably inconsequential

In addition to the typeglob annoyance, there is still a little repetition there: greet, inquire and bye still appear more than once in the code. I don't actually find this to be a huge deal, but how might we solve those issues? One way is including the code itself in the dispatch table:

  1. use strict;
  2. use warnings;
  3. my %dispatch = (
  4. # Documentation for greet can go here
  5. greet =>
  6. sub {
  7. my $greeting = shift || 'Howdy!';
  8. print "$greeting\n";
  9. },
  10. # Documentation for inquire can go here
  11. inquire =>
  12. sub {
  13. print "How are you?\n";
  14. },
  15. # Documentation for farewell can go here
  16. farewell =>
  17. sub {
  18. print "Bye!\n";
  19. },
  20. );
  21. sub delete_all_files { print "*KABOOM*" }
  22. sub api {
  23. my $call = shift;
  24. if ( $dispatch{$call} ) {
  25. $dispatch{$call}->(@_);
  26. }
  27. else {
  28. warn "Not executing unknown API call $call\n";
  29. }
  30. }
  31. api('greet','Hello.');
  32. api('inquire');
  33. api('farewell');
  34. api('delete_all_files');

Output:

Hello.
How are you?
Bye!
Not executing unknown API call delete_all_files

One argument against this is it adds visual complexity to the code: it's one more layer that a new developer on the project would need to mentally parse before coming up-to-speed on the code. But, that may be minor, and I think these formatting choices are developer-friendly.

4 comments:

Dean said...

You could use attributes to register subs in to dispatch tables, something like

#!perl
use strict;use warnings;

use My::PackageBase;

sub actionone : Table('foo') {}
sub actiontwo : Table('foo') {}
sub actionttree : Table('bar') {}

launch->('foo','actiontwo', @myargs);

#!perl
use strict;use warnings;

package My::PackageBase;

# export stuff

my %dispatchtables;

sub launch {
my ($table,$action,@args) = @_;
die "Unknown $table:$action" unless $dispatchtables{$table}{$action};
$dispatchtables{$table}{$action}->(@args);
}

# lifted from Attribute::Handlers
my %symcache;
sub findsym {
my ($pkg, $ref, $type) = @_;
return $symcache{$pkg,$ref} if $symcache{$pkg,$ref};
$type ||= ref($ref);
no strict 'refs';
for my $sym ( values %{$pkg."::"} ) {
use strict;
next unless ref ( \$sym ) eq 'GLOB';
return $symcache{$pkg,$ref} = \$sym
if *{$sym}{$type} && *{$sym}{$type} == $ref;
}
}

my $builtin = qr/lvalue|method|locked|unique|shared/;

sub MODIFY_CODE_ATTRIBUTES {
my ($package, $subref, @attrs) = @_;
my $sym = findsym($package, $subref);
my $name = *{$sym}{NAME};
for my $k (@attrs) {
if ($k =~ m/^Table\((['"]?)(?<table>[A-z][A-z0-9_\-]+)\1\)$/) {
my $table = $+{table}
$dispatchtable{$table}{$name} = $subref
}
}

return grep { defined && !/$builtin/ } @attrs;

}

1;


# the above isnt tested, i just copy and pasted it from other things im working on then chopped it up a bit as an example

nfg said...

Hmm. I think you can fix the second example by using "can". So switching from

sub build_dispatch_table {
no strict 'refs';
%dispatch = map { $_ => *{$_} } @valid_calls;
}

to

sub build_dispatch_table {
%dispatch = map { $_ => __PACKAGE__->can($_) } @valid_calls;
}

Now "@{$dispatch{greet}}" will become invalid code.

Anonymous said...

Dispatch tables aren't bad. What you are showing is "coding something incorrectly can result in bad things." Dispatch tables are no exception to that.

Unknown said...

In addition to using `can` to get a code ref you can just grab the CODE slot from the type globs:

%dispatch = map { $_ => *{$_}{CODE} } @valid_calls;