forked from SerenityOS/serenity
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Userland: Check sudoers file perms and owner in pls
As per comment found in SerenityOS#6319 by @bcoles, `pls` should check the permissions and owner of the sudoers file to ensure that it hasn't been compromised.
- Loading branch information
Showing
5 changed files
with
99 additions
and
86 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
# sudoers file | ||
# plsusers file | ||
# Put any users you want to allow to run programs as root here | ||
root | ||
anon |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,74 +1,59 @@ | ||
/* | ||
* Copyright (c) 2021 Jesse Buhagiar <[email protected]> | ||
* All rights reserved. | ||
* Copyright (c) 2021, Jesse Buhagiar <[email protected]> | ||
* | ||
* Redistribution and use in source and binary forms, with or without | ||
* modification, are permitted provided that the following conditions are met: | ||
* | ||
* 1. Redistributions of source code must retain the above copyright notice, this | ||
* list of conditions and the following disclaimer. | ||
* | ||
* 2. Redistributions in binary form must reproduce the above copyright notice, | ||
* this list of conditions and the following disclaimer in the documentation | ||
* and/or other materials provided with the distribution. | ||
* | ||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
* SPDX-License-Identifier: BSD-2-Clause | ||
*/ | ||
|
||
#include <AK/LexicalPath.h> | ||
#include <AK/String.h> | ||
#include <AK/StringBuilder.h> | ||
#include <AK/Types.h> | ||
#include <AK/Vector.h> | ||
#include <LibCore/Account.h> | ||
#include <LibCore/ArgsParser.h> | ||
#include <LibCore/DirIterator.h> | ||
#include <LibCore/File.h> | ||
#include <LibCore/GetPassword.h> | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <string.h> | ||
#include <unistd.h> | ||
|
||
static constexpr mode_t EXPECTED_PERMS = (S_IFREG | S_IRUSR); | ||
|
||
// Function Definitions | ||
extern "C" int main(int arch, char** argv); | ||
int unveil_paths(const char*); | ||
bool unveil_paths(const char*); | ||
|
||
// Unveil paths, given the current user's path and the command they want to execute | ||
int unveil_paths(const char* command) | ||
bool unveil_paths(const char* command) | ||
{ | ||
// Get the system path, split it and attempt to unveil all the paths. | ||
// We do NOT error out on an invalid path | ||
auto paths = String(getenv("PATH")).split(':'); | ||
int num_unveils = 0; | ||
char path_buf[256]; | ||
bool did_unveil_ok = false; | ||
|
||
// Unveil each path | ||
for (const auto& path : paths) { | ||
if (unveil(path.characters(), "x") == 0) | ||
num_unveils++; | ||
} | ||
// Attempt to unveil command via `realpath` | ||
auto* command_path = realpath(command, nullptr); | ||
|
||
// Now unveil the command | ||
auto command_path = realpath(command, &path_buf[0]); | ||
// Command found via `realpath` (meaning it was probably a locally executed program) | ||
if (command_path) { | ||
if (unveil(command_path, "x") == 0) | ||
num_unveils++; | ||
did_unveil_ok = true; | ||
|
||
free(command_path); | ||
return did_unveil_ok; | ||
} | ||
|
||
return num_unveils; | ||
// Okay, so we couldn't find the actual file specified by the user, let's | ||
// instead search PATH for it... | ||
auto command_path_system = Core::find_executable_in_path(command); | ||
if (command_path_system.is_empty()) | ||
return false; | ||
|
||
if (unveil(command_path_system.characters(), "x") == 0) | ||
did_unveil_ok = true; | ||
|
||
return did_unveil_ok; | ||
} | ||
|
||
// <kling> linusg: quaker: "please" feels quite long, how about "pls" :P | ||
// <kling> "pls rm -r crap" has a nice ring to it | ||
// lol | ||
int main(int argc, char** argv) | ||
{ | ||
Vector<const char*> command; | ||
|
@@ -82,12 +67,7 @@ int main(int argc, char** argv) | |
return 1; | ||
} | ||
|
||
if (seteuid(0) < 0) { | ||
perror("seteuid"); | ||
return 1; | ||
} | ||
|
||
if (unveil("/etc/sudoers", "r") < 0) { | ||
if (unveil("/etc/plsusers", "r") < 0) { | ||
perror("unveil"); | ||
return 1; | ||
} | ||
|
@@ -110,53 +90,74 @@ int main(int argc, char** argv) | |
// Unveil all paths in the user's PATH, as well as the command they've specified. | ||
auto unveil_count = unveil_paths(command.at(0)); | ||
if (unveil_count == 0) { | ||
warnln("Failed to unveil paths!"); | ||
warnln("Error: Failed to unveil paths!"); | ||
return 1; | ||
} | ||
|
||
// Lock veil | ||
unveil(nullptr, nullptr); | ||
|
||
const char* username = getlogin(); | ||
auto sudoers_file_or_error = Core::File::open("/etc/sudoers", Core::IODevice::ReadOnly); | ||
bool user_found = false; | ||
if (sudoers_file_or_error.is_error()) { | ||
warnln("couldn't open /etc/sudoers!"); | ||
// Call `seteuid` so we can access `/etc/plsusers` | ||
if (seteuid(0) < 0) { | ||
perror("seteuid"); | ||
return 1; | ||
} | ||
|
||
// Check the permissions and owner of /etc/plsusers. This ensures the integrity of the file. | ||
struct stat pls_users_stat; | ||
if (stat("/etc/plsusers", &pls_users_stat) < 0) { | ||
perror("stat"); | ||
return 1; | ||
} | ||
|
||
for (auto line = sudoers_file_or_error.value()->line_begin(); !line.at_end(); ++line) { | ||
if (pls_users_stat.st_mode != EXPECTED_PERMS) { | ||
warnln("Error: /etc/plsusers has incorrect permissions."); | ||
return 4; | ||
} | ||
|
||
if (pls_users_stat.st_uid != 0 && pls_users_stat.st_gid != 0) { | ||
warnln("Error: /etc/plsusers is not owned by root."); | ||
return 4; | ||
} | ||
|
||
auto pls_users_file_or_error = Core::File::open("/etc/plsusers", Core::OpenMode::ReadOnly); | ||
if (pls_users_file_or_error.is_error()) { | ||
warnln("Error: Could not open /etc/plsusers: {}", pls_users_file_or_error.error()); | ||
return 1; | ||
} | ||
|
||
const char* username = getlogin(); | ||
bool user_found = false; | ||
for (auto line = pls_users_file_or_error.value()->line_begin(); !line.at_end(); ++line) { | ||
auto line_str = *line; | ||
|
||
// Skip any comments | ||
if (line_str.starts_with("#")) | ||
continue; | ||
|
||
// Our user is in the sudoers file! | ||
// Our user is in the plsusers file! | ||
if (line_str.to_string() == username) { | ||
user_found = true; | ||
break; | ||
} | ||
} | ||
|
||
// User isn't in the sudoer's file | ||
// User isn't in the plsusers file | ||
if (!user_found) { | ||
warnln("{} is not in the sudoers file!", username); | ||
warnln("{} is not in the plsusers file!", username); | ||
return 2; | ||
} | ||
|
||
// The user was in the sudoers file, now let's ask for their password to ensure that it's actually them... | ||
uid_t current_uid = getuid(); | ||
auto account_or_error = (username) | ||
? Core::Account::from_name(username) | ||
: Core::Account::from_uid(current_uid); | ||
// The user was in the plsusers file, now let's ask for their password to ensure that it's actually them... | ||
auto account_or_error = Core::Account::from_name(username); | ||
|
||
if (account_or_error.is_error()) { | ||
warnln("Core::Account::from_name: {}", account_or_error.error()); | ||
return 1; | ||
} | ||
|
||
const auto& account = account_or_error.value(); | ||
uid_t current_uid = getuid(); | ||
if (current_uid != 0 && account.has_password()) { | ||
auto password = Core::get_password(); | ||
if (password.is_error()) { | ||
|
@@ -171,40 +172,48 @@ int main(int argc, char** argv) | |
} | ||
|
||
// TODO: Support swapping users instead of just defaulting to root | ||
setgid(0); | ||
setuid(0); | ||
if (setgid(0) < 0) { | ||
perror("setgid"); | ||
return 1; | ||
} | ||
|
||
if (setuid(0) < 0) { | ||
perror("setuid"); | ||
return 1; | ||
} | ||
|
||
// Build the arguments list passed to `execvpe` | ||
Vector<const char*> exec_args; | ||
for (size_t i = 0; i < command.size(); i++) { | ||
exec_args.append(command.at(i)); | ||
for (const auto& arg : command) { | ||
exec_args.append(arg); | ||
} | ||
|
||
// Always terminate with a NULL (to signal end of args list) | ||
exec_args.append(nullptr); | ||
|
||
// Build the environment arguments | ||
StringBuilder builder; | ||
Vector<String> env_args_str; | ||
|
||
// TERM envvar | ||
char* env_term = getenv("TERM"); | ||
|
||
// Build SUDO_USER envvar | ||
builder.append("SUDO_USER="); | ||
builder.append(username); | ||
auto sudo_user = builder.build(); | ||
builder.clear(); | ||
if (env_term != nullptr) { | ||
builder.append("TERM="); | ||
builder.append(env_term); | ||
env_args_str.append(builder.build()); | ||
} | ||
|
||
// Build SUDO_COMMAND envvar | ||
builder.append("SUDO_COMMAND="); | ||
builder.append(command.at(0)); | ||
auto sudo_command = builder.build(); | ||
builder.clear(); | ||
Vector<const char*> env_args; | ||
for (auto& arg : env_args_str) { | ||
env_args.append(arg.characters()); | ||
} | ||
|
||
const char* envs[] = { "PROMPT=\\X\\u@\\h:\\w\\a\\e[33;1m\\h\\e[0m \\e[34;1m\\w\\e[0m \\p ", | ||
"TERM=xterm", "PAGER=more", "PATH=/bin:/usr/bin:/usr/local/bin", | ||
sudo_user.characters(), sudo_command.characters(), nullptr }; | ||
// Arguments list must be terminated with NULL argument | ||
env_args.append(nullptr); | ||
|
||
// Execute the desired command | ||
int rc = execvpe(command.at(0), const_cast<char**>(exec_args.data()), const_cast<char**>(envs)); | ||
if (rc < 0) { | ||
if (execvpe(command.at(0), const_cast<char**>(exec_args.data()), const_cast<char**>(env_args.data())) < 0) { | ||
perror("execvpe"); | ||
exit(1); | ||
} | ||
|