1

My end goal is to have a script that can be initially launched by a non-privileged user without using sudo, but will prompt for sudo password and self-elevate to root. I've been doing this with a bash wrapper script but would like something tidier that doesn't need an additional file.

Some googling found this question on StackOverflow where the accepted answer suggesting using os.execlpe to re-launch the script while retaining the same environment. I tried it, but it immediately failed to import a non-built-in module on the second run.

Investigating revealed that the PYTHONPATH variable is not carried over, while almost every other environment variable is (PERL5LIB is also missing, and a couple of others, but I'm not using them so they're not troubling me).

I have a brief little test script that demonstrates the issue:

#!/usr/bin/env python
import os
import sys

print(len(os.environ['PYTHONPATH']))

euid = os.geteuid()
if euid != 0:
    print("Script not started as root. Running with sudo.")
    args = ['sudo', sys,executable] + sys.argv + [os.environ]
    os.execlpe('sudo', *args)

print("Success")

Expected output would be:

6548
Script not started as root. Running with sudo.
[sudo] password for esker:
6548
Success

But instead I'm getting a KeyError:

6548
Script not started as root. Running with sudo.
[sudo] password for esker:
Traceback (most recent call last):
  File "/usr/home/esker/execlpe_test.py", line 5, in <module>
    print(len(os.environ['PYTHONPATH']))
  File "/vol/apps/python/2.7.6/lib/python2.7/UserDict.py", line 23, in __getitem__
    raise KeyError(key)
KeyError: 'PYTHONPATH'

What would be the cause of this missing variable, and how can I avoid it disappearing? Alternatively, is there a better way about doing this that won't result in running into the problem?

Esker
  • 11
  • 1
  • I believe the superuser environment differs from normal. Correct me if I am wrong? – cs95 Jun 15 '17 at 05:16
  • What happens when you add the "PYTHONPATH" to the os.environ ? – Tamar Jun 15 '17 at 05:17
  • @Coldspeed The superuser environment does differ, yes, which is why I'm wanting to replace it with the user's os.environ. – Esker Jun 15 '17 at 05:21
  • @Tamar I don't have the contents of PYTHONPATH on the second launch in order to add it. os.execlpe doesn't subprocess, it replaces the current process. – Esker Jun 15 '17 at 05:23
  • the os.environ should be the third argument to function – Tamar Jun 15 '17 at 05:31
  • @Tamar [The docs](https://docs.python.org/2/library/os.html#os.execlpe) say it should be the last variable. I gave switching the order around a go regardless and it did indeed error. – Esker Jun 15 '17 at 05:42

2 Answers2

0

I found this very weird too, and couldn't find any direct way to pass the environment into the replaced process. But I didn't do a full system debugging either.

What I found to work as a workaround is this:

pypath = os.environ.get('PYTHONPATH', "")
args = ['sudo', f"PYTHONPATH={pypath}", sys.executable] + sys.argv
os.execvpe('sudo', args, os.environ)

I.e. explicitly pass PYTHONPATH= to the new process. Note that I prefer to use os.execvpe(), but it works the same with the other exec*(), given the correct call. See this answer for a good overview of the schema.

However, PATH and the rest of the environment is still it's own environment, as an initial print(os.environ) shows. But PYTHONPATH will be passed on this way.

Janos
  • 796
  • 1
  • 9
  • 26
-1

You're passing the environment as arguments to your script instead of arguments to execlpe. Try this instead:

args = ['sudo', sys,executable] + sys.argv + [os.environ]
os.execvpe('sudo', args, os.environ)

If you just want to inherit the environment you can even

os.execvp('sudo', args)
emulbreh
  • 3,421
  • 23
  • 27
  • 2
    I've given that a go and (after stripping os.environ off the end of args) and the issue is still occuring. It's not that the environment as whole isn't being passed through to the new process (most of it is), it's that the PYTHONPATH variable specifically isn't. – Esker Jun 15 '17 at 07:26
  • I don't think this answer makes any sense or improves the solution of the OP. You're adding `[os.environ]` to both the `args` list **and** pass it again as an arg to `execvpe()` - that can't work. See this great answer on how the `exec*` functions differ. Also the not about "inherit the environment" is not correct - without passing the environment, nothing is inherited. – Janos Dec 02 '22 at 14:36