-
Notifications
You must be signed in to change notification settings - Fork 234
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
[RFC] Add --exec / -e to su
for shell-bypass
#254
base: master
Are you sure you want to change the base?
Conversation
In preparation for supporting --exec I was testing the robustness of "--" handling and it became apparent that things are currently a bit broken in `su`. Since "--" is currently of limited utility, as the subsequent words are simply passed to the shell after "-c","command_string", it seems to have gone unnoticed for ages. However, with --exec, it's expected that "--" would be an almost required separator with every such usage, considering the following flags must be passed verbatim to execve() and will likely begin with hyphens looking indistinguishable from any other flags in lieu of shell interpolation to worry about. For some practical context of the existing situation, this invocation doesn't work today: ``` $ su --command ls -- flags for shell No passwd entry for user 'flags' $ ``` This should just run ls as root with "flags","for","shell" forwarded to the shell after "-c","ls". The "--" should block "flags" from being treated as the user. That particular issue isn't a getopt one per-se, it's arguably just a bug in su.c's implementation. It *seemed* like an easy fix for this would be to add a check if argv[optind-1] were "--" before treating argv[optind] as USER. But testing that fix revealed getopt was rearranging things when encountering "--", the "--" would always separate the handled opts from the unhandled ones. USER would become shifted to *after* "--" even when it occurred before it! If we change the command to specify the user, it works as-is: ``` $ su --command ls root -- flags for shell Password: testfile $ ``` But what's rather surprising is how that works; the argv winds up: "su","--command","ls","--","root","flags","for","shell" with optind pointing at "root". That arrangement of argv is indistinguishable from omitting the user and having "root","flags","for","shell" as the stuff after "--". This makes it non-trivial to fix the bug of omitting user treating the first word after "--" as the user, which one could argue is a potentially serious security bug if you omit the user, expect the command to run as root, and the first word after "--" is a valid user, and what follows that something valid and potentially destructive not only running in unintended form but as whatever user happened to be the first word after "--". So, it seems like something important to fix, and getopt seems to be getting in the way of fixing it properly without being more trouble than replacing getopt. In disbelief of what I was seeing getopt doing with argv here, I took a glance at the getopt source and found the following: ``` /* The special ARGV-element '--' means premature end of options. Skip it like a null option, then exchange with previous non-options as if it were an option, then skip everything else like a non-option. */ if (d->optind != argc && !strcmp (argv[d->optind], "--")) ``` I basically never use getopt personally because ages ago it annoyed me with its terrible API for what little it brought to the table, and this brings it to a whole new level of awful.
Mechanical rename distinguishing this variable from intended changes supporting executing commands without using an interpretive shell (i.e. no '/bin/sh -c').
It's now possible to run commands as other users without shell interpolation by using "--exec": Read /etc/shadow as root without specifying user: ``` su --exec /bin/cat -- /etc/shadow ``` Or specify user: ``` su --exec /bin/cat root -- /etc/shadow ```
su
for shell-bypasssu
for shell-bypass
Thanks - this looks good to me. I'm a bit skiddish about the possibility that, however correct it is, it might break someone's usage in some script somewhere from way back when. So I'm going to take another look with fresh eyes soon, before I merge. |
Actually, you marked this as 'draft' - are you intending to make more changes? |
@hallyn Well, I didn't update the docs in this PR, which I presumed would be necessary before merging. So I figured leave it as an RFC draft in case we need to bikeshed a bit on the flag naming or anything else before updating the docs/man page. But no, I didn't have any planned future modifications to the code. My only apprehension with this PR is I fairly quickly did the getopt replacement in frustration and didn't make an exhaustive effort to verify I replicated exactly all of the error messages getopt might produce with bad flags. I made some effort, but I didn't go crazy with it, like I didn't scrutinize the getopt code to run down all the error paths and ensure I replicated all of them. I'm not sure how important that aspect is. |
Hi, in PR #256 I proposed an alternate fix for the 'user' handling in su. While doing this I noticed that the argument handling for -c is wrong anyway - it appears if you do
It will do
but it would need to do
for the arguments to get sent to the command. It seems an easy enough fix, but I didn't do it just now. |
@vcaputo how would you feel about rebasing this patch set on top of PR#256 instead of patch 1/3 ? |
ping? I may go ahead and rebase it myself this weekend if you don't have time but don't object. |
See comments in #256 |
BTW as demonstrated in #256, the way the POSIX shell handles subsequent arguments following I don't know anyone who would naturally think to add explicit parameters suffixing the
predicated on the realization that there's another shell gating things in the middle there, so go look at its man page about its -c handling. So, if some form of --exec lands, I'd like to see mentioning something about this in the man page - perhaps even promoting --exec use over --command or demoting --command in the manual as something you should only use if shell features are required. The way argument passing behaves with --exec is much more sane IMHO. |
I don't think this PR is needed, as |
@HarmtH I presume you meant It's not equivalent because that's And if you attempt to somehow contort this into working by mixing
|
Yes, I meant |
Here's the relevant code in su https://github.com/shadow-maint/shadow/blob/master/src/su.c#L1110 |
Yes, I thought I'd mentioned that in my pr but I guess I edited that out in a rebase. I'm going to think just a bit more about this and then probably merge it and update the manpage. |
I'm in no hurry - it'll be forever before this percolates into my molasses-like distro anyways. What started out as just wanting to add a shell bypass mode to Honestly, I didn't exhaustively test this PR, because it was apparent that there'd appropriately be some friction, and likely discussion and iteration necessary if it would land at all. I just made things presentable enough to try move things forward, get the ball rolling on upstream understanding what had been uncovered. So definitely take care in sanity checking this stuff before merging. I'll see if I can make some time to do more testing of this PR myself as well. Let's not replace an existing argument handling bug with new ones out of haste, especially with a suid binary. |
Yeah - I was planning on adding some tests based on the ones you showed above. Thanks for this. |
I meant an example which showes the necessity / use case of the controlling terminal being closed 😅 Maybe unnecessary to say, but having a controlling terminal and being an interactive shell are two entirely different concepts. You can have a non-interactive shell with a controlling terminal, and you can have an interactive shell without a controlling terminal (although you won't have job control then). Btw, as soon as you give additional arguments to a (non-)shell passed to |
This adds the ability to run commands via
su
without an interpretive shell, enabling a thinner user-switching veneer over execve() mode of operation.