Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Setting UID (e.g. dropping root privileges) #133

Closed
tomekwojcik opened this issue Mar 26, 2013 · 5 comments
Closed

Setting UID (e.g. dropping root privileges) #133

tomekwojcik opened this issue Mar 26, 2013 · 5 comments

Comments

@tomekwojcik
Copy link
Contributor

Hello,
I'm working on adding _uid special keyword argument and I thought I'd ask for suggestions before actually opening a pull request. The code in question is available here.

Now, as you can see this is clearly a dirty hack at the moment. What I'd like to know is how to properly handle the situation when the parent process isn't owned by root (currently raising RuntimeError). Also, I have to explicitly turn off TTYs on stdin and stdout (which isn't that much of a problem, I guess).

Would you even consider adding this functionality to the library?

Best regards.

EDIT:
An example of the feature in action:

twojcik-mb:sh bilbo$ sudo python -c "import sh; print sh.whoami().stdout.strip(); print sh.whoami(_uid=501).stdout.strip()"
root
bilbo
@amoffat
Copy link
Owner

amoffat commented Mar 26, 2013

Hi Tomek

I like it and would merge this feature. I think the behavior of raising a RuntimeError if non-root tries to use setuid makes sense. The other alternative is to quietly fail (and launch the subprocess as the user that owns the parent process), but this might have security implications if the user was expecting _uid to work.

I'm curious why TTYs need to be turned off though?

Also, be sure to add a unit test in test.py You'll probably want to create a temporary script in your unit test that prints one thing if uid is one value, and another thing if the uid is another value, then test this output. Take a look at how the other tests do this.

@tomekwojcik
Copy link
Contributor Author

Hi Andrew,
I totally agree on security implications of silencing the error when non-root user is trying to change UID of a process. Personally, I'd much rather see the exception being raised as it indicates a problem somewhere on my end.

As far as turning of the TTYs - if I don't do so the process hangs and I have to ^C after running a command, e.g.

Shire:sh bilbo$ sudo python
Python 2.7.3 (default, Oct 25 2012, 22:00:57)
[GCC 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.1.00)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import sh
>>> sh.whoami()
root

>>> sh.whoami(_uid=501)
^CTraceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "sh.py", line 731, in __call__
    return RunningCommand(cmd, call_args, stdin, stdout, stderr)
  File "sh.py", line 292, in __init__
    self.wait()
  File "sh.py", line 296, in wait
    self._handle_exit_code(self.process.wait())
  File "sh.py", line 1117, in wait
    pid, exit_code = os.waitpid(self.pid, 0)
KeyboardInterrupt
>>> sh.whoami(_uid=501, _tty_out=False, _tty_in=False)
bilbo

>>> 

I have no idea what's the problem here. Permissions, maybe? Nevertheless, I think that if the docs mention switching off TTYs when using _uid then everything should be fine.

I've been thinking a lot about testing the feature, but I see a small problem here - the test would have to be run by root, so setting UID is possible. Any ideas how to tackle this problem?

One more question has just came to my mind - which Python versions do you wish me to test before opening a pull request? I'm thinking 2.6, 2.7, 3.3.

Anyways, thanks for the lib - it's been very helpful and made my life much easier :).

@amoffat
Copy link
Owner

amoffat commented Mar 27, 2013

Ah I was able to reproduce the TTY/uid issue. However, when I ran my test script, I got an exception:

22:21:02 $> sudo python uid_tty_fail.py 
Traceback (most recent call last):
  File "uid_tty_fail.py", line 3, in <module>
    print sh.whoami(_uid=1000)
  File "/home/amoffat/workspace/pbs/sh.py", line 731, in __call__
    return RunningCommand(cmd, call_args, stdin, stdout, stderr)
  File "/home/amoffat/workspace/pbs/sh.py", line 292, in __init__
    self.wait()
  File "/home/amoffat/workspace/pbs/sh.py", line 296, in wait
    self._handle_exit_code(self.process.wait())
  File "/home/amoffat/workspace/pbs/sh.py", line 310, in _handle_exit_code
    self.process.stderr
sh.ErrorReturnCode_1: 

  RAN: '/usr/bin/whoami'

  STDOUT:


  STDERR:
Traceback (most recent call last):
  File "uid_tty_fail.py", line 3, in <module>
    print sh.whoami(_uid=1000)
  File "/home/amoffat/workspace/pbs/sh.py", line 731, in __call__
    return RunningCommand(cmd, call_args, stdin, stdout, stderr)
  File "/home/amoffat/workspace/pbs/sh.py", line 289, in __init__
    self.call_args, pipe=pipe)
  File "/home/amoffat/workspace/pbs/sh.py", line 855, in __init__
    tmp_fd = os.open(os.ttyname(1), os.O_RDWR)
OSError: [Errno 13] Permission denied: '/dev/pts/3'

If I do what you did in the terminal though, no exception is raised, which is odd. But for this, I think it's clear that the setuid is happening too soon. Can you try moving it closer to the os.execve and see if that works for you?

I've been thinking a lot about testing the feature, but I see a small problem here - the test would have to be run by root, so setting UID is possible. Any ideas how to tackle this problem?

I think it makes sense to wrap that particular unit test in a "skip" decorator. Take a look at requires_posix and how it's used in test.py. The new decorator could be called requires_root and tests if the executing script has uid == 0.

One more question has just came to my mind - which Python versions do you wish me to test before opening a pull request? I'm thinking 2.6, 2.7, 3.3.

All 3 if possible. If you run python sh.py test, it should automate testing all the versions of python you have installed.

Anyways, thanks for the lib - it's been very helpful and made my life much easier :).

Very happy to hear it, I'm glad it's helpful :)

@tomekwojcik
Copy link
Contributor Author

OK, so I moved the UID setting part before os.execve call and it works :). See the current code here.

I'm gonna perform some tests on the code that is the root cause of my need to change UID (it's a part of a project I'm working on at work) and see if it works as expected.

As far as functional and compatibility tests go - thanks for your input. I'll keep that in mind while writing test code and performing compatibility tests. Targeting Python 3.x isn't a problem for me - I already have the interpreters built and ready to use.

Once I'm done I'll open a pull request so you can review the final code.

@amoffat
Copy link
Owner

amoffat commented Oct 14, 2016

Merged what was in your branch into the 1.2 release and it will go out then. Thanks for your contribution 👍

0-wiz-0 added a commit to NetBSD/pkgsrc-wip that referenced this issue Dec 12, 2016
*   added `_out` and `_out_bufsize` validator [#346](amoffat/sh#346)
*   bugfix for internal stdout thread running when it shouldn't [#346](amoffat/sh#346)

*   regression bugfix on timeout [#344](amoffat/sh#344)
*   regression bugfix on `_ok_code=None`

*   further improvements on cpu usage

*   regression in cpu usage [#339](amoffat/sh#339)

*   fd leak regression and fix for flawed fd leak detection test [#337](amoffat/sh#337)

*   support for `io.StringIO` in python2

*   added support for using raw file descriptors for `_in`, `_out`, and `_err`
*   removed `.close()`ing `_out` handler if FIFO detected

*   composed commands no longer propagate `_bg`
*   better support for using `sys.stdin` and `sys.stdout` for `_in` and `_out`
*   bugfix where `which()` would not stop searching at the first valid executable found in PATH
*   added `_long_prefix` for programs whose long arguments start with something other than `--` [#278](amoffat/sh#278)
*   added `_log_msg` for advanced configuration of log message [#311](amoffat/sh#311)
*   added `sh.contrib.sudo`
*   added `_arg_preprocess` for advanced command wrapping
*   alter callable `_in` arguments to signify completion with falsy chunk
*   bugfix where pipes passed into `_out` or `_err` were not flushed on process end [#252](amoffat/sh#252)
*   deprecated `with sh.args(**kwargs)` in favor of `sh2 = sh(**kwargs)`
*   made `sh.pushd` thread safe
*   added `.kill_group()` and `.signal_group()` methods for better process control [#237](amoffat/sh#237)
*   added `new_session` special keyword argument for controlling spawned process session [#266](amoffat/sh#266)
*   bugfix better handling for EINTR on system calls [#292](amoffat/sh#292)
*   bugfix where with-contexts were not threadsafe [#247](amoffat/sh#195)
*   `_uid` new special keyword param for specifying the user id of the process [#133](amoffat/sh#133)
*   bugfix where exceptions were swallowed by processes that weren't waited on [#309](amoffat/sh#309)
*   bugfix where processes that dupd their stdout/stderr to a long running child process would cause sh to hang [#310](amoffat/sh#310)
*   improved logging output [#323](amoffat/sh#323)
*   bugfix for python3+ where binary data was passed into a process's stdin [#325](amoffat/sh#325)
*   Introduced execution contexts which allow baking of common special keyword arguments into all commands [#269](amoffat/sh#269)
*   `Command` and `which` now can take an optional `paths` parameter which specifies the search paths [#226](amoffat/sh#226)
*   `_preexec_fn` option for executing a function after the child process forks but before it execs [#260](amoffat/sh#260)
*   `_fg` reintroduced, with limited functionality.  hurrah! [#92](amoffat/sh#92)
*   bugfix where a command would block if passed a fd for stdin that wasn't yet ready to read [#253](amoffat/sh#253)
*   `_long_sep` can now take `None` which splits the long form arguments into individual arguments [#258](amoffat/sh#258)
*   making `_piped` perform "direct" piping by default (linking fds together).  this fixes memory problems [#270](amoffat/sh#270)
*   bugfix where calling `next()` on an iterable process that has raised `StopIteration`, hangs [#273](amoffat/sh#273)
*   `sh.cd` called with no arguments no changes into the user's home directory, like native `cd` [#275](amoffat/sh#275)
*   `sh.glob` removed entirely.  the rationale is correctness over hand-holding. [#279](amoffat/sh#279)
*   added `_truncate_exc`, defaulting to `True`, which tells our exceptions to truncate output.
*   bugfix for exceptions whose messages contained unicode
*   `_done` callback no longer assumes you want your command put in the background.
*   `_done` callback is now called asynchronously in a separate thread.
*   `_done` callback is called regardless of exception, which is necessary in order to release held resources, for example a process pool
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants