NAME

Unix::Sudo - Run a block of code as root

DESCRIPTION

Run a block of code as root

SYNOPSIS

As a normal user who can sudo ...

use Unix::Sudo qw(sudo);

print `whoami`;          # shows your normal username
sudo {
    eval "use Taint::Runtime qw(disable)";
    print `whoami`;      # root
};
print `whoami`;          # back to normal

EXPORTS / FUNCTIONS

There is one function, which can be exported if you wish but is not exported by default:

sudo

Takes a code-ref as its only argument. This can be in a variable, or as an anonymous code-ref, or a block:

my $code = sub { 1 + 2 };
sudo($code);

sudo(sub { 1 + 2 });

sudo { 1 + 2 };

That code-ref will be executed as root, with no arguments, and with as much as possible of the calling code's environment. It may return an integer value from 0 to 254. Anything else will be numified.

If you want to return something more complicated then I recommend that you return it on STDOUT and use Capture::Tiny to retrieve it.

ERRORS

A return value of 255 is special and is used to indicate that the code either died, couldn't be compiled, or did something else blatantly illegal. When this happens the child process will spit an error message to STDERR, and the parent will die and attempt to tell you where you passed dodgy code to Unix::Sudo.

See CAVEATS for some hints on circumstances when this might happen.

HOW IT WORKS

Internally, your code will be de-parsed into its text form and then executed thus:

system(
    'sudo', '-p', '...', 'perl', '-T',
    '-I...', ...
    '-e',
    "exit(do{ $your_code_here })"
) >> 8;

sudo might have to prompt for a password. If it does, then the prompt will make it clear that this is Unix::Sudo asking for it.

It's not just your code that is passed to the child process. There are also a bunch of -I arguments, so that it knows about any directories in the parent process's @INC, and it will also get copies of all the lexical variables that are in scope in the calling code.

Under the bonnet it uses B::Deparse to turn your code-ref into text, PadWalker's peek_my() to get variables, and Data::Dumper (and $Data::Dumper::Deparse) to turn those variables into text, all of which is pre-pended to your code.

CAVEATS

Your code will always have strict and warnings turned on, and be run with taint-checking enabled. If you need to you can turn tainting off as shown in the synopsis. Note that you can't just say 'use Taint::Runtime qw(disable)', the eval is required, otherwise the use will be run at compile-time in the calling code and not in the child process where you need it.

If your code needs to use any modules, or any subroutines that are imported, you will need to say so inside the code-ref you pass. And again, remember that you'll have to eval any use statements.

The variables that are passed through to your code are read-write, but any changes you make are local to the child process so will not be communicated back to the parent process.

Any blessed references, tied variables, or objects that use overload in those variables may not behave as you expect. For example, a record that has been read from a database won't have an active database connection; something tied to a filehandle won't have an open filehandle; an object that uses overload to make reading its value have side-effects will not have those side-effects respected in the parent process. In general, you should use this to "promote" as little of your code as possible to run as root, and only your code so that you can be as aware as possible of the preceding.

Any global variables in the Unix::Sudo namespace and any environment variables beginning with UNIX_SUDO_ are reserved.

Calling sudo() from within sudo() is not supported.

DEBUGGING

If your code isn't behaving as you expect or is dieing then I recommend that you set UNIX_SUDO_SPILLGUTS=1 in your environment. This will cause Unix::Sudo to warn() you about what it is about to execute before it does so.

SECURITY CONCERNS

This code will run potentially user-supplied code as root. I have done what I can to avoid security hilarity, but if you allow someone to pass rm -rf /* that's your problem.

I have mitigated potential problems by:

using the LIST form of system

It shouldn't be possible to craft input that makes my code run your code as root and then make my code run something else as root.

It is of course still possible to make my code run your code as root and for your code to then run other stuff as root.

tainting is turned on

That means that any input to your code from the outside world is internally marked as being untrusted, and you are restricted in what you can do with it. You can of course circumvent this by untainting, either in the usual regexy ways or as noted above via Taint::Runtime.

I strongly recommend that you read and understand the source code and also read perlsec before using this code.

STABILITY

I make no promises about the stability of the interface, and it is subject to change without notice. This is because I want to strongly encourage you to read the documentation and the source code before installing new versions of this code.

I also therefore urge you that if you use this module in anything important you should "pin" it to a particular version number in whatever you use for managing your dependencies.

BUGS/FEEDBACK

Please report bugs at https://github.com/DrHyde/perl-modules-Unix-Sudo/issues, including, if possible, a test case.

SOURCE CODE REPOSITORY

git://github.com/DrHyde/perl-modules-Unix-Sudo.git

AUTHOR, COPYRIGHT and LICENCE

Copyright 2024 David Cantrell <david@cantrell.org.uk>

This software is free-as-in-speech software, and may be used, distributed, and modified under the terms of either the GNU General Public Licence version 2 or the Artistic Licence. It's up to you which one you use. The full text of the licences can be found in the files GPL2.txt and ARTISTIC.txt, respectively.

CONSPIRACY

This module is also free-as-in-mason software.