diff options
-rw-r--r-- | Base/etc/sudoers | 4 | ||||
-rw-r--r-- | Base/usr/share/man/man8/pls.md | 41 | ||||
-rw-r--r-- | Documentation/BuildInstructions.md | 2 | ||||
-rwxr-xr-x | Meta/build-root-filesystem.sh | 1 | ||||
-rw-r--r-- | Userland/Utilities/CMakeLists.txt | 1 | ||||
-rw-r--r-- | Userland/Utilities/pls.cpp | 213 |
6 files changed, 262 insertions, 0 deletions
diff --git a/Base/etc/sudoers b/Base/etc/sudoers new file mode 100644 index 0000000000..a301b3aac1 --- /dev/null +++ b/Base/etc/sudoers @@ -0,0 +1,4 @@ +# sudoers file +# Put any users you want to allow to run programs as root here +root +anon diff --git a/Base/usr/share/man/man8/pls.md b/Base/usr/share/man/man8/pls.md new file mode 100644 index 0000000000..3bf540d374 --- /dev/null +++ b/Base/usr/share/man/man8/pls.md @@ -0,0 +1,41 @@ +## Name + +pls - Execute a command as root + +## Synopsis + +```**sh +$ pls [command] +``` + +## Description + +Executes a command as the root user (uid and gid 0), given that the user executing `pls` is located in +the sudoers file. + +It is possible to execute commands that contain hyphenated options via the use of `--`, which signifies the +end of command options. For example: + +```sh +$ pls -- ls -la +``` + +## Files +/etc/sudoers - List of users that can run `pls` + +## Examples + +```sh +$ pls whoami +Password: +root +$ +``` + +```sh +$ pls sh +Password: +# whoami +root +# +``` diff --git a/Documentation/BuildInstructions.md b/Documentation/BuildInstructions.md index 438bace435..5ed034c240 100644 --- a/Documentation/BuildInstructions.md +++ b/Documentation/BuildInstructions.md @@ -230,6 +230,8 @@ $ ninja run Note that the `anon` user is able to become `root` without password by default, as a development convenience. To prevent this, remove `anon` from the `wheel` group and he will no longer be able to run `/bin/su`. +`anon` is also, by default, located in `/etc/sudoers`, meaning that they will be able to execute with root permission using `pls`. +To prevent this, remove them from `/etc/sudoers`. On Linux, QEMU is significantly faster if it's able to use KVM. The run script will automatically enable KVM if `/dev/kvm` exists and is readable+writable by the current user. diff --git a/Meta/build-root-filesystem.sh b/Meta/build-root-filesystem.sh index bbbac6c5fe..9249eef183 100755 --- a/Meta/build-root-filesystem.sh +++ b/Meta/build-root-filesystem.sh @@ -63,6 +63,7 @@ chmod 0400 mnt/res/kernel.map chmod 0400 mnt/boot/Kernel chmod 4750 mnt/bin/su chmod 4755 mnt/bin/passwd +chmod 4751 mnt/bin/pls chmod 4755 mnt/bin/ping chmod 4755 mnt/bin/traceroute chmod 4750 mnt/bin/reboot diff --git a/Userland/Utilities/CMakeLists.txt b/Userland/Utilities/CMakeLists.txt index db281f1c7b..db1d414a2b 100644 --- a/Userland/Utilities/CMakeLists.txt +++ b/Userland/Utilities/CMakeLists.txt @@ -42,6 +42,7 @@ target_link_libraries(open LibDesktop) target_link_libraries(pape LibGUI) target_link_libraries(passwd LibCrypt) target_link_libraries(paste LibGUI) +target_link_libraries(pls LibCrypt) target_link_libraries(pro LibProtocol) target_link_libraries(shot LibGUI) target_link_libraries(sql LibLine LibSQL) diff --git a/Userland/Utilities/pls.cpp b/Userland/Utilities/pls.cpp new file mode 100644 index 0000000000..eb56f2d333 --- /dev/null +++ b/Userland/Utilities/pls.cpp @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2021 Jesse Buhagiar <jooster669@gmail.com> + * All rights reserved. + * + * 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. + */ + +#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/File.h> +#include <LibCore/GetPassword.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +// Function Definitions +extern "C" int main(int arch, char** argv); +int 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) +{ + // 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]; + + // Unveil each path + for (const auto& path : paths) { + if (unveil(path.characters(), "x") == 0) + num_unveils++; + } + + // Now unveil the command + auto command_path = realpath(command, &path_buf[0]); + if (command_path) { + if (unveil(command_path, "x") == 0) + num_unveils++; + } + + return num_unveils; +} + +// <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; + Core::ArgsParser args_parser; + args_parser.add_positional_argument(command, "Command to run at elevated privilege level", "command"); + + args_parser.parse(argc, argv); + + if (pledge("stdio tty rpath exec id", nullptr) < 0) { + perror("pledge"); + return 1; + } + + if (seteuid(0) < 0) { + perror("seteuid"); + return 1; + } + + if (unveil("/etc/sudoers", "r") < 0) { + perror("unveil"); + return 1; + } + + if (unveil("/etc/passwd", "r") < 0) { + perror("unveil"); + return 1; + } + + if (unveil("/etc/shadow", "r") < 0) { + perror("unveil"); + return 1; + } + + if (unveil("/etc/group", "r") < 0) { + perror("unveil"); + return 1; + } + + // 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!"); + 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!"); + return 1; + } + + for (auto line = sudoers_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! + if (line_str.to_string() == username) { + user_found = true; + break; + } + } + + // User isn't in the sudoer's file + if (!user_found) { + warnln("{} is not in the sudoers 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); + + if (account_or_error.is_error()) { + warnln("Core::Account::from_name: {}", account_or_error.error()); + return 1; + } + + const auto& account = account_or_error.value(); + if (current_uid != 0 && account.has_password()) { + auto password = Core::get_password(); + if (password.is_error()) { + warnln("{}", password.error()); + return 1; + } + + if (!account.authenticate(password.value().characters())) { + warnln("Incorrect or disabled password."); + return 1; + } + } + + // TODO: Support swapping users instead of just defaulting to root + setgid(0); + setuid(0); + + // 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)); + } + + // Always terminate with a NULL (to signal end of args list) + exec_args.append(nullptr); + + // Build the environment arguments + StringBuilder builder; + + // Build SUDO_USER envvar + builder.append("SUDO_USER="); + builder.append(username); + auto sudo_user = builder.build(); + builder.clear(); + + // Build SUDO_COMMAND envvar + builder.append("SUDO_COMMAND="); + builder.append(command.at(0)); + auto sudo_command = builder.build(); + builder.clear(); + + 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 }; + + // Execute the desired command + int rc = execvpe(command.at(0), const_cast<char**>(exec_args.data()), const_cast<char**>(envs)); + if (rc < 0) { + perror("execvpe"); + exit(1); + } + + return 0; +} |