http://qs1969.pair.com?node_id=902050

elTriberium has asked for the wisdom of the Perl Monks concerning the following question:

Hi,

I want to dynamically call subroutines based on user input. I know that there are some security concerns with that, but in this scenario I think it should work.

Here's the basic idea:

my $tcid; GetOptions("tcid=s" => \$tcid); my @tcids = split(",", $tcid); my %dispatch_table; foreach my $tc (@tcids) { $dispatch_table{$tc} = sub {eval "tcid_$tc()"}, );
Then, later on call it with:
$dispatch_table{$tc}->();

It's basically a big test file, which supports running individual test case ids, which can be passed in through the command line.

My question now is: Since there are some security concerns for this and it requires some manual setup (creating the dispatch table, checking whether the sub exists at all, etc.) is there already a module which provides the required functionality? I thought that I can't be the first one ever having to run individual subs based on user input.

Thanks!

Replies are listed 'Best First'.
Re: "Dynamic" dispatch tables
by roboticus (Chancellor) on Apr 29, 2011 at 18:12 UTC

    elTriberium:

    It's really pretty simple to use a dispatch table, so no module is really required. For example:

    $ cat disp_tbl.pl #!/usr/bin/perl use strict; use warnings; use feature ':5.10'; sub foo { say "foobar" } sub bar { say "barbaz" } my %DT = ( foo=>\&foo, bar=>\&bar, quit=>sub { die "END!"; } ); say "Commands are foo, bar and quit\n"; while (<>) { chomp; if (exists $DT{$_}) { $DT{$_}(); } } $ perl disp_tbl.pl Commands are foo, bar and quit foo foobar bar barbaz quit END! at disp_tbl.pl line 9, <> line 3.

    Update: By themselves, dispatch tables aren't a security concern. As long as the user isn't able to have perl (or the shell, database...) execute a string that the user enters, then everything is under the programs control.

    Update: Ooops! mr_mischief mentioned that very thing as I was editing my node.

    ...roboticus

    When your only tool is a hammer, all problems look like your thumb.

Re: "Dynamic" dispatch tables
by mr_mischief (Monsignor) on Apr 29, 2011 at 18:14 UTC

    All interactive software tends to run different subs based on user input in some way. There's no security issue with that per se. Where there's a security issue is making a system call with user-supplied data or executing code received as user-supplied data. If one of your subroutines makes a change to part of the system outside your program, you need to be very sure what kinds of changes it can make given the inputs which are allowed to change its behavior.

      Right, that's the part where user input becomes code in my original post:
      $dispatch_table{$tc} = sub {eval "tcid_$tc()"},
      $tc is a value entered by the user. I'm dynamically creating the sub name tcid_$tc based on that input. Since this is in an eval, there are of course bad things a user could do here (like supplying --tcid 1;<bad code here>), but since this code will never be released to the public I don't see this as a big issue. And of course I can add some additional parsing for example that a tcid is only \d+ and nothing else.
        Well, if the only people running the code would also have access to alter the code then you're not really protecting the system from anything but an accident.
Re: "Dynamic" dispatch tables
by Tanktalus (Canon) on Apr 29, 2011 at 20:14 UTC

    I'm not sure why you're using a dispatch table here :-)

    sub tcid_1 { say 1 } sub tcid_2 { say 2 } sub tcid_3 { say 3 } sub tcid_4 { say 4 } my @tcids = 1..3; my @funcs = map { if (not exists &{"tcid_$_"}) { die "Invalid tcid: $_"; } \&{"tcid_$_"}; } @tcids; for my $f (@funcs) { $f->(); }
    There's no need for a hash if you're just running them in sequence anyway.

      I'm not sure why you're using a dispatch table here...

      The table offers slightly more security, in that there's no possibility of unguarded user input somehow selecting an inappropriate function.

      Even so, namespacing as you've done ameliorates that somewhat.

        In this case, I cordially suggest that the concern is not “the use of dispatch-tables per se,” but the authentication that must take place to confirm that a particular dispatch-table entry may be used.

        When I construct dispatch tables, I usually employ a hashref where each element points to another hashref with several entries in it.   One of these of course is a reference to the sub that must be called to do the work.   Others may be to subs that serve as parameter-validators and security checkers ... you can get as fancy as you like, even setting up arrays of these subroutine references.

        And then, when the target routine actually does get control, it, too, I believe, has the same duty to check once again that it is being called under appropriate conditions.   (Those checks can be simpler, since they basically serve to double-check that the dispatcher is doing its job.)   Only requests that have been scrutinized several times, by code that exists in several different places, actually get carried out.

      You're right, I'm not sure yet if I really need a hash. I'll have to figure out some of the details.

      I also already started using the "not exists" part, but running into a problem with that: I want to move the setup of the dispatch table / array into a module, but in the module the "exists &{"tcid_$_"} always fails as the sub is not defined in this module. Is there a good workaround for this?

        Yes. What package will they be in? If they'll be in the Foo package, then just use exists &{"Foo::tcid_$_"} instead. :-)