Skip to content

Commit

Permalink
runuser: new applet
Browse files Browse the repository at this point in the history
Add a cut down, Windows-specific implementation of `runuser` from
util-linux.

This allows elevated privileges to be dropped when running in an
SSH session.  It also works when using `su` or starting busybox-w32
'as administrator'.

There are complications:

- The method used to drop privileges leaves the access token in the
  TokenIsElevated state.  Detecting this is likely to be fragile.

- The unprivileged shell is started by CreateProcessAsUserA().  In
  older versions of Windows this has to be loaded dynamically.

Adds about 900 bytes.

(GitHub issue rmyorston#240)
  • Loading branch information
rmyorston committed Mar 13, 2023
1 parent 6eeb524 commit 385decd
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 15 deletions.
3 changes: 2 additions & 1 deletion configs/mingw32_defconfig
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#
# Automatically generated make config: don't edit
# Busybox version: 1.37.0.git
# Tue Feb 7 09:34:52 2023
# Sun Mar 12 09:41:00 2023
#
CONFIG_HAVE_DOT_CONFIG=y
# CONFIG_PLATFORM_POSIX is not set
Expand Down Expand Up @@ -707,6 +707,7 @@ CONFIG_XXD=y
# CONFIG_RENICE is not set
CONFIG_REV=y
# CONFIG_RTCWAKE is not set
CONFIG_RUNUSER=y
# CONFIG_SCRIPT is not set
# CONFIG_SCRIPTREPLAY is not set
# CONFIG_SETARCH is not set
Expand Down
3 changes: 2 additions & 1 deletion configs/mingw64_defconfig
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#
# Automatically generated make config: don't edit
# Busybox version: 1.37.0.git
# Tue Feb 7 09:34:52 2023
# Sun Mar 12 09:41:00 2023
#
CONFIG_HAVE_DOT_CONFIG=y
# CONFIG_PLATFORM_POSIX is not set
Expand Down Expand Up @@ -707,6 +707,7 @@ CONFIG_XXD=y
# CONFIG_RENICE is not set
CONFIG_REV=y
# CONFIG_RTCWAKE is not set
CONFIG_RUNUSER=y
# CONFIG_SCRIPT is not set
# CONFIG_SCRIPTREPLAY is not set
# CONFIG_SETARCH is not set
Expand Down
2 changes: 2 additions & 0 deletions include/mingw.h
Original file line number Diff line number Diff line change
Expand Up @@ -590,3 +590,5 @@ int has_path(const char *file);
int is_relative_path(const char *path);
char *get_last_slash(const char *path);
const char *applet_to_exe(const char *name);
char *get_user_name(void);
char *quote_arg(const char *arg);
117 changes: 117 additions & 0 deletions util-linux/runuser.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/* vi: set sw=4 ts=4: */
/*
* runuser - run a shell without elevated privileges.
* This is a much restricted, Windows-specific reimplementation of
* runuser from util-linux.
*
* Copyright (c) 2023 Ronald M Yorston
*
* Licensed under GPLv2 or later, see file LICENSE in this source tree.
*/
//config:config RUNUSER
//config: bool "runuser"
//config: default y
//config: depends on PLATFORM_MINGW32 && SH_IS_ASH
//config: help
//config: Run a shell without elevated privileges

//applet:IF_RUNUSER(APPLET(runuser, BB_DIR_USR_BIN, BB_SUID_DROP))

//kbuild:lib-$(CONFIG_RUNUSER) += runuser.o

//usage:#define runuser_trivial_usage
//usage: "USER [ARG...]"
//usage:#define runuser_full_usage "\n\n"
//usage: "Run a shell without elevated privileges. The user name\n"
//usage: "must be that of the user who was granted those privileges.\n"
//usage: "Any arguments are passed to the shell.\n"

#include "libbb.h"
#include <winsafer.h>
#include <lazyload.h>

int runuser_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int runuser_main(int argc UNUSED_PARAM, char **argv)
{
const char *user;
SAFER_LEVEL_HANDLE safer;
HANDLE token;
STARTUPINFO si;
PROCESS_INFORMATION pi;
TOKEN_MANDATORY_LABEL TIL;
// Medium integrity level S-1-16-8192
unsigned char medium[12] = {
0x01, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x10,
0x00, 0x20, 0x00, 0x00
};
char *cmd, **a;
DWORD code;
// This shouldn't be necessary but without it the binary complains
// it can't find CreateProcessAsUserA on older versions of Windows.
DECLARE_PROC_ADDR(BOOL, CreateProcessAsUserA, HANDLE, LPCSTR, LPSTR,
LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, BOOL, DWORD,
LPVOID, LPCSTR, LPSTARTUPINFOA, LPPROCESS_INFORMATION);

if (!INIT_PROC_ADDR(advapi32.dll, CreateProcessAsUserA))
bb_simple_error_msg_and_die("not supported");

if (getuid() != 0)
bb_simple_error_msg_and_die("may not be used by non-root users");

if (argc < 2)
bb_show_usage();

user = get_user_name();
if (user == NULL || strcmp(argv[1], user) != 0)
bb_simple_error_msg_and_die("invalid user");

/*
* Run a shell using a token with reduced privilege. Hints from:
*
* https://stackoverflow.com/questions/17765568/
*/
if (SaferCreateLevel(SAFER_SCOPEID_USER, SAFER_LEVELID_NORMALUSER,
SAFER_LEVEL_OPEN, &safer, NULL) &&
SaferComputeTokenFromLevel(safer, NULL, &token, 0, NULL)) {

// Set medium integrity
TIL.Label.Sid = (PSID)medium;
TIL.Label.Attributes = SE_GROUP_INTEGRITY;
if (SetTokenInformation(token, TokenIntegrityLevel, &TIL,
sizeof(TOKEN_MANDATORY_LABEL))) {

ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
si.dwFlags = STARTF_USESTDHANDLES;

// Build the command line
cmd = xstrdup("sh");
for (a = argv + 2; *a; ++a) {
char *q = quote_arg(*a);
char *newcmd = xasprintf("%s %s", cmd, q);
if (q != *a)
free(q);
free(cmd);
cmd = newcmd;
}

if (!CreateProcessAsUserA(token, bb_busybox_exec_path,
cmd, NULL, NULL, TRUE, 0, NULL,
NULL, &si, &pi)) {
errno = err_win_to_posix();
bb_perror_msg_and_die("can't execute 'sh'");
}

WaitForSingleObject(pi.hProcess, INFINITE);
if (GetExitCodeProcess(pi.hProcess, &code)) {
return (int)code;
}
}
}

return EXIT_FAILURE;
}
43 changes: 31 additions & 12 deletions win32/mingw.c
Original file line number Diff line number Diff line change
Expand Up @@ -1109,7 +1109,7 @@ static char *getsysdir(void)
}

#define NAME_LEN 100
static char *get_user_name(void)
char *get_user_name(void)
{
static char *user_name = NULL;
char *s;
Expand All @@ -1136,18 +1136,42 @@ static char *get_user_name(void)
return user_name;
}

#if ENABLE_RUNUSER
/*
* When runuser drops privileges TokenIsElevated still returns TRUE.
* Use other means to determine if we're actually unprivileged.
* This is likely to be fragile.
*/
static int
actually_unprivileged(HANDLE h)
{
DWORD restricted = 0;
DWORD size;

if (GetTokenInformation(h, TokenHasRestrictions, &restricted,
sizeof(restricted), &size)) {
// The token generated by runuser seems to 'have restrictions'.
return restricted != 0;
}

return FALSE;
}
#else
# define actually_unprivileged(h) (FALSE)
#endif

int getuid(void)
{
int ret = DEFAULT_UID;
HANDLE h;

if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &h)) {
TOKEN_ELEVATION elevation;
DWORD size = sizeof(TOKEN_ELEVATION);
TOKEN_ELEVATION elevation = { 0 };
DWORD size;

if (GetTokenInformation(h, TokenElevation, &elevation,
sizeof(elevation), &size)) {
if (elevation.TokenIsElevated)
if (elevation.TokenIsElevated && !actually_unprivileged(h))
ret = 0;
}
CloseHandle(h);
Expand All @@ -1174,17 +1198,12 @@ struct passwd *getpwuid(uid_t uid)
{
static struct passwd p;

if (uid == 0) {
if (uid == 0)
p.pw_name = (char *)"root";
p.pw_dir = getsysdir();
}
else if (uid == DEFAULT_UID && (p.pw_name=get_user_name()) != NULL) {
p.pw_dir = gethomedir();
}
else {
else if (uid != DEFAULT_UID || (p.pw_name=get_user_name()) == NULL)
return NULL;
}

p.pw_dir = gethomedir();
p.pw_passwd = (char *)"";
p.pw_gecos = p.pw_name;
p.pw_shell = NULL;
Expand Down
2 changes: 1 addition & 1 deletion win32/process.c
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ parse_interpreter(const char *cmd, interp_t *interp)
* See https://docs.microsoft.com/en-us/cpp/cpp/main-function-command-line-args?view=vs-2019#parsing-c-command-line-arguments
* (Parsing C++ Command-Line Arguments)
*/
static char *
char *
quote_arg(const char *arg)
{
int len = 0, n = 0;
Expand Down

0 comments on commit 385decd

Please sign in to comment.