0

This question has been asked here in multiple forms. I am asking it again because all these questions had too many details. Hence the answers all boiled down to how to solve those specific problems without jumping between users.
This I why am posting this as a new question (and immediately answering it below) for others that have this problem.

Suppose you have a perl script that you run as root where you first want to run things as root, then things as a regular user and then as root again.

For example:

#!/usr/bin/perl
#Problem 1: Make sure to start as root
system("whoami");
#Problem 2: Become your regular user
system("whoami");
#Problem 3: Become root again
system("whoami);

should be changed to show:

root
your_username
root
Garo
  • 1,339
  • 12
  • 21
  • What are you trying to do? Most server processes are started as root, do what they need to do as root (e.g., bind to a privileged port), and then drop root privileges. This is done for security reasons --- if there is a bug in the code, damage is limited if the process runs under a limited service account, not root. – Robert Nov 09 '22 at 18:50
  • In my case it's a script that I run on a linux live medium and that creates a fully customized distro on a hard disk. Some things I can do as root, some things should be done as another user. Other solutions are possible (e.g. using multiple scripts and calling them with a `system("sudo -u someuser otherscript.pl)");`, but for my use case 1 script is the best. – Garo Nov 09 '22 at 23:15
  • If I ever saw something in a system trying to do something like this, I'd have them figure out a different way to do it. – brian d foy Nov 10 '22 at 15:39
  • Links to the other questions? Curious what's going on here. – brian d foy Nov 10 '22 at 15:39
  • @briandfoy I have to agree that for most problems better/safer solutions exist. But for my use-case it's a good method. As answer for those other questions (GIYF) it's usually bad (because they are working on "real" systems) – Garo Nov 10 '22 at 15:55
  • And, you have to remember that Stackoverflow is a repository of info for other people to use. You're presenting a solution to a very specific problem that you don't state, and someone else is going to do this because it's here. I suggest stating your use case and adding strong language why nothing else worked for you. – brian d foy Nov 10 '22 at 16:00
  • Does your system support saved user IDs? If so [`Unix::SavedIDs`](https://metacpan.org/pod/Unix::SavedIDs) might help. However, single-process priv changing is easy to get wrong without the complication of saved IDs. (And don't forget your supplementary groups!) – pilcrow Nov 11 '22 at 20:30

1 Answers1

3

This the best solution I can think of.

If you want to start as root, become a regular user and become root again:

#!/usr/bin/perl
use POSIX qw/setuid waitpid/;
exec("sudo", $0, @ARGV) unless($< == 0);  #Restart the program as root if you are a regular user
system("whoami");
my $pid = fork;  #create a extra copy of the program
if($pid == 0) {
  #This block will contain code that should run as a regular user
  setuid(1000);  #So switch to that user (e.g. the one with UID 1000)
  system("whoami");
  exit;  #make sure the child stops running once the task for the regular user are done
}
#Everything after this will run in the parent where we are still root
waitpid($pid, 0); #wait until the code of the child has finished
system("whoami");

When starting as a regular user it's best to make sure that the parent stays a regular user and the child becomes root. You can do this like this:

#!/usr/bin/perl
use POSIX qw/setuid waitpid/;
unless($< == 0) {
  #regular user code, this is the first code that will run
  system("whoami");
  #now fork, let the child become root and let the parent wait for the child
  my $pid = fork;
  exec("sudo", $0, @ARGV) if($pid == 0);
  waitpid($pid, 0);
  #continue with regular user code, this is the 3th part that will run
  system("whoami");
  exit; #the end of the program has been reached, exit or we would continue with code meant for root
}
#code for root, this is the 2nd part that will run
system("whoami");
  
  
ikegami
  • 367,544
  • 15
  • 269
  • 518
Garo
  • 1,339
  • 12
  • 21
  • 1
    `$^X, '--', $0, @ARGV` would be safer and more reliable than `$0, @ARGV` – ikegami Nov 09 '22 at 18:25
  • 1
    Passing `POSIX::WUNTRACED` instead of `0` to `waitpid` seems wrong. Did you really mean to stop waiting before the child exits? – ikegami Nov 09 '22 at 18:27
  • 1
    Tip: No need to import `waitpid` from POSIX.pm. It simply calls the builtin `waitpid` that would have been called had you not imported the same-named sub from POSIX.pm. – ikegami Nov 09 '22 at 18:30
  • @ikegami About your 1st comment: That would also work but i don't get why it would be more safe/reliable, you already specify the perl interpreter you are using in the shebang of the script. – Garo Nov 09 '22 at 23:38
  • @ikegami About your 2nd comment: I do indeed want to wait until the child exits, and this should be the case. Put a `sleep 10;` before `system("whoami");` in the child to check it. But it seems that `0` has the same effect, thanks I did not know this. – Garo Nov 09 '22 at 23:41
  • @ikegami About your 3th comment: You are right. But the reason that i am using the one in POSIX.pm is that I'm loading this module anyway and I am assuming *(but I don't know if this is a correct assumption)* that the one in POSIX.pm is the one that is the most stable / up-to-date – Garo Nov 09 '22 at 23:48
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/249485/discussion-between-garo-and-ikegami). – Garo Nov 10 '22 at 14:51
  • The `$^X` is what you want because you are also passing an environment to your child process. It's easy to get mismatched binary problems otherwise (and there are plenty of questions on SO about that). – brian d foy Nov 10 '22 at 16:02