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

Drop root privileges before forking #333

Merged
merged 1 commit into from
May 30, 2024

Conversation

teto519f
Copy link

@teto519f teto519f commented May 24, 2024

Implemented a way for lo2s to detect a call with sudo and then drop its privileges before launching any process. Different methods of gaining root aren't taken into account (yet).

This refers to #301

To Do:

  • test functionality in real use case
  • ensure compatibility with lo2s -a
  • look into doas support (then update manpage)

@teto519f
Copy link
Author

With current changes you cannot call on any process, that needs sudo through lo2s.
For example lo2s -- sudo apt update does not work, since lo2s can't call on sudo without root privileges.
sudo lo2s -- sudo apt update should be something you're able to do (imo), but this doesn't work when dropping privileges.

TL;DR I would like to add an option, that disables the sudo/doas check. Is this valid?

@cvonelm
Copy link
Member

cvonelm commented May 27, 2024

TL;DR I would like to add an option, that disables the sudo/doas check. Is this valid?

I would do it the other way around, enabling this getuid functionality only if some option is given. This feels like something that might lead to weird-to-detect errors if lo2s would just do privilege dropping automatically

Ideally, this option would also accept some username, so that you could specify that you want to switch to that specific user.

You can get the UID/GID of some username by using getpwnam (https://linux.die.net/man/3/getpwnam)

@teto519f
Copy link
Author

This way a user could forget to add the option. Question is which case is worse to run into: accidentally launching a process with root or potentially having some weird errors.

And regarding the username-thing: I have a weird feeling about giving a user the ability to act as every other user. Nothing would stop them from switching to some daemon or a nologin user.

@teto519f teto519f changed the title WIP: Drop root privileges before forking Drop root privileges before forking May 28, 2024
@teto519f teto519f marked this pull request as ready for review May 28, 2024 06:40
@teto519f teto519f marked this pull request as draft May 28, 2024 08:53
@teto519f teto519f marked this pull request as ready for review May 28, 2024 10:05
else
{
// sudo is garanteed in this case, see check in config.hpp
original_uid = atoi(std::getenv("SUDO_UID"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use stoi() instead of atoi()

Print an error message if stoi can not parse SUDO_UID to a number

auto orig_user = getpwnam(config().user.c_str());
if (orig_user == nullptr)
{
Log::error() << "No user found with specified username.";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

print which user failed here

man/lo2s.1.pod Outdated
@@ -154,6 +154,14 @@ determine the trace path instead, including variable substitution.
Attach to a running process with process ID I<PID> instead of launching
I<COMMAND>.

=item B<-u>, B<--as-current-user>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally dont like "current" in the option and in the text wording, maybe --as-pre-sudo-user?

man/lo2s.1.pod Outdated

=item B<-U>, B<--as-user> I<USERNAME>

Launch I<COMMAND> as the specified user. Implies B<-u> but does not require sudo, but the current user has to have setuid() privileges. Will take priority over B<-u> if specified together.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

user I instead of "the specified user" . I think the part about "implies" in its current implementation confuses more than it helps

src/config.cpp Outdated
.toggle("as-current-user", "Drop root privileges to current user before launching COMMAND.")
.short_name("u");

general_options.option("as-user", "Specify a user to drop privileges to.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change wording in accordance with man page comment above.

src/config.cpp Outdated
@@ -421,6 +431,18 @@ void parse_program_options(int argc, const char** argv)
}
}

if (arguments.given("as-current-user") && std::getenv("SUDO_UID") == NULL)
{
Log::error() << "-u was specified but no sudo was used.";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

used -> detected

man/lo2s.1.pod Outdated

=item B<-U>, B<--as-user> I<USERNAME>

Launch I<COMMAND> as the specified user. Implies B<-u> but does not require sudo, but the current user has to have setuid() privileges. Will take priority over B<-u> if specified together.
Launch I<COMMAND> as I<USERNAME>. I<USERNAME> has to have setuid() privileges. Will take priority over B<-u> if specified together.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

USERNAME is not the user that needs to be able to do setuid. The user we are before we use setuid() to switch to USERNAME needs to be able to do setuid().

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think "the caller" or "the calling user" is a good description for this user? Just wanted to ask before I commit again.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"the caller needs to have CAP_SETUID" is the most correct way to write it me thinks

@cvonelm
Copy link
Member

cvonelm commented May 30, 2024

I have tested this now with our main use-case (which is running the IOR benchmark with mpirun, while lo2s runs as root) which seems to run fine, so we can check of testing

@cvonelm cvonelm merged commit 8e13667 into tud-zih-energy:master May 30, 2024
21 checks passed
@@ -185,6 +185,14 @@ void parse_program_options(int argc, const char** argv)
.metavar("PID")
.optional();

general_options.toggle("as-pre-sudo-user", "Drop root privileges before launching COMMAND.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The internal config option is called drop_root, the description is "Drop root privileges ...", but the thing is called --as-pre-sudo-user. I think that is confusing. Don't differ between "internal" and "external" naming of features. I think --drop-root(-privileges) would have been a better name.

general_options.toggle("as-pre-sudo-user", "Drop root privileges before launching COMMAND.")
.short_name("u");

general_options.option("as-user", "User to drop privileges to before launching COMMAND.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is easier to understand:
"Launch the COMMAND as the user USERNAME"

@@ -421,6 +430,18 @@ void parse_program_options(int argc, const char** argv)
}
}

if (arguments.given("as-pre-sudo-user") && std::getenv("SUDO_UID") == NULL)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not the behavior I would expect.
While lo2s -a -A works fine, lo2s -u -U $USER complains about -u was specified but no sudo was detected.

Or in other words, -U should imply -u.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, in C++, we don't use NULL. Use nullptr instead.

}
else
{
// sudo is garanteed in this case, see check in config.hpp
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't only put your assumptions into comments. Put them in assert()s.

}
catch (std::invalid_argument const& e)
{
Log::error() << "Cannot parse SUDO_UID/SUDO_GID into int.";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be a user-facing message. Users don't know what an int is or that SUDO_UID/SUDO_GID are environment variables.

Cannot parse the environment variables SUDO_UID and/or SUDO_GID.

catch (std::invalid_argument const& e)
{
Log::error() << "Cannot parse SUDO_UID/SUDO_GID into int.";
throw_errno();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std::stoi raises an exception, but does not set errno. throw_errno() would need that.

Replace with throw;, which rethrows the current exception.

Log::error() << "Cannot parse SUDO_UID/SUDO_GID into int.";
throw_errno();
}
catch (std::out_of_range const& e)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the distinction between both exceptions types adds any value here. I'd rather have one "parsing failed" message.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you remove the second catch statement and catch general exceptions or duplicate the error handling for both types?
I'd go for the second option, but redundant code is wack and a function would be overkill.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd go with one handler catch(const std::exception& e). As users typically do not set these environment variables themselves, I don't think any other message than "failed to parse ..." is necessary, but rather confusing.

catch (std::out_of_range const& e)
{
Log::error() << "SUDO_UID/SUDO_GID out of range.";
throw_errno();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

throw_errno() doesn't work here, but remove it completely.

teto519f pushed a commit to teto519f/lo2s that referenced this pull request Jun 4, 2024
bmario added a commit that referenced this pull request Jul 11, 2024
Edit setuid() options as requested in PR #333
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants