aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRon Yorston <rmy@pobox.com>2023-03-13 10:25:56 +0000
committerRon Yorston <rmy@pobox.com>2023-03-13 10:25:56 +0000
commit385decd6bf62c116565ece1e0992ff7a79d48474 (patch)
tree30065938d54231dcadf54cc3e22206f4985a7180
parent6eeb5240974bb304830319e9fa5afbc4d6194fc0 (diff)
downloadbusybox-w32-385decd6bf62c116565ece1e0992ff7a79d48474.tar.gz
busybox-w32-385decd6bf62c116565ece1e0992ff7a79d48474.tar.bz2
busybox-w32-385decd6bf62c116565ece1e0992ff7a79d48474.zip
runuser: new applet
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 #240)
-rw-r--r--configs/mingw32_defconfig3
-rw-r--r--configs/mingw64_defconfig3
-rw-r--r--include/mingw.h2
-rw-r--r--util-linux/runuser.c117
-rw-r--r--win32/mingw.c43
-rw-r--r--win32/process.c2
6 files changed, 155 insertions, 15 deletions
diff --git a/configs/mingw32_defconfig b/configs/mingw32_defconfig
index 360ea3b50..611fd327a 100644
--- a/configs/mingw32_defconfig
+++ b/configs/mingw32_defconfig
@@ -1,7 +1,7 @@
1# 1#
2# Automatically generated make config: don't edit 2# Automatically generated make config: don't edit
3# Busybox version: 1.37.0.git 3# Busybox version: 1.37.0.git
4# Tue Feb 7 09:34:52 2023 4# Sun Mar 12 09:41:00 2023
5# 5#
6CONFIG_HAVE_DOT_CONFIG=y 6CONFIG_HAVE_DOT_CONFIG=y
7# CONFIG_PLATFORM_POSIX is not set 7# CONFIG_PLATFORM_POSIX is not set
@@ -707,6 +707,7 @@ CONFIG_XXD=y
707# CONFIG_RENICE is not set 707# CONFIG_RENICE is not set
708CONFIG_REV=y 708CONFIG_REV=y
709# CONFIG_RTCWAKE is not set 709# CONFIG_RTCWAKE is not set
710CONFIG_RUNUSER=y
710# CONFIG_SCRIPT is not set 711# CONFIG_SCRIPT is not set
711# CONFIG_SCRIPTREPLAY is not set 712# CONFIG_SCRIPTREPLAY is not set
712# CONFIG_SETARCH is not set 713# CONFIG_SETARCH is not set
diff --git a/configs/mingw64_defconfig b/configs/mingw64_defconfig
index 0dc1d0d51..6db0accbf 100644
--- a/configs/mingw64_defconfig
+++ b/configs/mingw64_defconfig
@@ -1,7 +1,7 @@
1# 1#
2# Automatically generated make config: don't edit 2# Automatically generated make config: don't edit
3# Busybox version: 1.37.0.git 3# Busybox version: 1.37.0.git
4# Tue Feb 7 09:34:52 2023 4# Sun Mar 12 09:41:00 2023
5# 5#
6CONFIG_HAVE_DOT_CONFIG=y 6CONFIG_HAVE_DOT_CONFIG=y
7# CONFIG_PLATFORM_POSIX is not set 7# CONFIG_PLATFORM_POSIX is not set
@@ -707,6 +707,7 @@ CONFIG_XXD=y
707# CONFIG_RENICE is not set 707# CONFIG_RENICE is not set
708CONFIG_REV=y 708CONFIG_REV=y
709# CONFIG_RTCWAKE is not set 709# CONFIG_RTCWAKE is not set
710CONFIG_RUNUSER=y
710# CONFIG_SCRIPT is not set 711# CONFIG_SCRIPT is not set
711# CONFIG_SCRIPTREPLAY is not set 712# CONFIG_SCRIPTREPLAY is not set
712# CONFIG_SETARCH is not set 713# CONFIG_SETARCH is not set
diff --git a/include/mingw.h b/include/mingw.h
index b5b2fe169..e49483307 100644
--- a/include/mingw.h
+++ b/include/mingw.h
@@ -590,3 +590,5 @@ int has_path(const char *file);
590int is_relative_path(const char *path); 590int is_relative_path(const char *path);
591char *get_last_slash(const char *path); 591char *get_last_slash(const char *path);
592const char *applet_to_exe(const char *name); 592const char *applet_to_exe(const char *name);
593char *get_user_name(void);
594char *quote_arg(const char *arg);
diff --git a/util-linux/runuser.c b/util-linux/runuser.c
new file mode 100644
index 000000000..f6abd9a74
--- /dev/null
+++ b/util-linux/runuser.c
@@ -0,0 +1,117 @@
1/* vi: set sw=4 ts=4: */
2/*
3 * runuser - run a shell without elevated privileges.
4 * This is a much restricted, Windows-specific reimplementation of
5 * runuser from util-linux.
6 *
7 * Copyright (c) 2023 Ronald M Yorston
8 *
9 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
10 */
11//config:config RUNUSER
12//config: bool "runuser"
13//config: default y
14//config: depends on PLATFORM_MINGW32 && SH_IS_ASH
15//config: help
16//config: Run a shell without elevated privileges
17
18//applet:IF_RUNUSER(APPLET(runuser, BB_DIR_USR_BIN, BB_SUID_DROP))
19
20//kbuild:lib-$(CONFIG_RUNUSER) += runuser.o
21
22//usage:#define runuser_trivial_usage
23//usage: "USER [ARG...]"
24//usage:#define runuser_full_usage "\n\n"
25//usage: "Run a shell without elevated privileges. The user name\n"
26//usage: "must be that of the user who was granted those privileges.\n"
27//usage: "Any arguments are passed to the shell.\n"
28
29#include "libbb.h"
30#include <winsafer.h>
31#include <lazyload.h>
32
33int runuser_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
34int runuser_main(int argc UNUSED_PARAM, char **argv)
35{
36 const char *user;
37 SAFER_LEVEL_HANDLE safer;
38 HANDLE token;
39 STARTUPINFO si;
40 PROCESS_INFORMATION pi;
41 TOKEN_MANDATORY_LABEL TIL;
42 // Medium integrity level S-1-16-8192
43 unsigned char medium[12] = {
44 0x01, 0x01, 0x00, 0x00,
45 0x00, 0x00, 0x00, 0x10,
46 0x00, 0x20, 0x00, 0x00
47 };
48 char *cmd, **a;
49 DWORD code;
50 // This shouldn't be necessary but without it the binary complains
51 // it can't find CreateProcessAsUserA on older versions of Windows.
52 DECLARE_PROC_ADDR(BOOL, CreateProcessAsUserA, HANDLE, LPCSTR, LPSTR,
53 LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, BOOL, DWORD,
54 LPVOID, LPCSTR, LPSTARTUPINFOA, LPPROCESS_INFORMATION);
55
56 if (!INIT_PROC_ADDR(advapi32.dll, CreateProcessAsUserA))
57 bb_simple_error_msg_and_die("not supported");
58
59 if (getuid() != 0)
60 bb_simple_error_msg_and_die("may not be used by non-root users");
61
62 if (argc < 2)
63 bb_show_usage();
64
65 user = get_user_name();
66 if (user == NULL || strcmp(argv[1], user) != 0)
67 bb_simple_error_msg_and_die("invalid user");
68
69 /*
70 * Run a shell using a token with reduced privilege. Hints from:
71 *
72 * https://stackoverflow.com/questions/17765568/
73 */
74 if (SaferCreateLevel(SAFER_SCOPEID_USER, SAFER_LEVELID_NORMALUSER,
75 SAFER_LEVEL_OPEN, &safer, NULL) &&
76 SaferComputeTokenFromLevel(safer, NULL, &token, 0, NULL)) {
77
78 // Set medium integrity
79 TIL.Label.Sid = (PSID)medium;
80 TIL.Label.Attributes = SE_GROUP_INTEGRITY;
81 if (SetTokenInformation(token, TokenIntegrityLevel, &TIL,
82 sizeof(TOKEN_MANDATORY_LABEL))) {
83
84 ZeroMemory(&si, sizeof(STARTUPINFO));
85 si.cb = sizeof(STARTUPINFO);
86 si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
87 si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
88 si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
89 si.dwFlags = STARTF_USESTDHANDLES;
90
91 // Build the command line
92 cmd = xstrdup("sh");
93 for (a = argv + 2; *a; ++a) {
94 char *q = quote_arg(*a);
95 char *newcmd = xasprintf("%s %s", cmd, q);
96 if (q != *a)
97 free(q);
98 free(cmd);
99 cmd = newcmd;
100 }
101
102 if (!CreateProcessAsUserA(token, bb_busybox_exec_path,
103 cmd, NULL, NULL, TRUE, 0, NULL,
104 NULL, &si, &pi)) {
105 errno = err_win_to_posix();
106 bb_perror_msg_and_die("can't execute 'sh'");
107 }
108
109 WaitForSingleObject(pi.hProcess, INFINITE);
110 if (GetExitCodeProcess(pi.hProcess, &code)) {
111 return (int)code;
112 }
113 }
114 }
115
116 return EXIT_FAILURE;
117}
diff --git a/win32/mingw.c b/win32/mingw.c
index 712728bd6..c7eeea088 100644
--- a/win32/mingw.c
+++ b/win32/mingw.c
@@ -1109,7 +1109,7 @@ static char *getsysdir(void)
1109} 1109}
1110 1110
1111#define NAME_LEN 100 1111#define NAME_LEN 100
1112static char *get_user_name(void) 1112char *get_user_name(void)
1113{ 1113{
1114 static char *user_name = NULL; 1114 static char *user_name = NULL;
1115 char *s; 1115 char *s;
@@ -1136,18 +1136,42 @@ static char *get_user_name(void)
1136 return user_name; 1136 return user_name;
1137} 1137}
1138 1138
1139#if ENABLE_RUNUSER
1140/*
1141 * When runuser drops privileges TokenIsElevated still returns TRUE.
1142 * Use other means to determine if we're actually unprivileged.
1143 * This is likely to be fragile.
1144 */
1145static int
1146actually_unprivileged(HANDLE h)
1147{
1148 DWORD restricted = 0;
1149 DWORD size;
1150
1151 if (GetTokenInformation(h, TokenHasRestrictions, &restricted,
1152 sizeof(restricted), &size)) {
1153 // The token generated by runuser seems to 'have restrictions'.
1154 return restricted != 0;
1155 }
1156
1157 return FALSE;
1158}
1159#else
1160# define actually_unprivileged(h) (FALSE)
1161#endif
1162
1139int getuid(void) 1163int getuid(void)
1140{ 1164{
1141 int ret = DEFAULT_UID; 1165 int ret = DEFAULT_UID;
1142 HANDLE h; 1166 HANDLE h;
1143 1167
1144 if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &h)) { 1168 if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &h)) {
1145 TOKEN_ELEVATION elevation; 1169 TOKEN_ELEVATION elevation = { 0 };
1146 DWORD size = sizeof(TOKEN_ELEVATION); 1170 DWORD size;
1147 1171
1148 if (GetTokenInformation(h, TokenElevation, &elevation, 1172 if (GetTokenInformation(h, TokenElevation, &elevation,
1149 sizeof(elevation), &size)) { 1173 sizeof(elevation), &size)) {
1150 if (elevation.TokenIsElevated) 1174 if (elevation.TokenIsElevated && !actually_unprivileged(h))
1151 ret = 0; 1175 ret = 0;
1152 } 1176 }
1153 CloseHandle(h); 1177 CloseHandle(h);
@@ -1174,17 +1198,12 @@ struct passwd *getpwuid(uid_t uid)
1174{ 1198{
1175 static struct passwd p; 1199 static struct passwd p;
1176 1200
1177 if (uid == 0) { 1201 if (uid == 0)
1178 p.pw_name = (char *)"root"; 1202 p.pw_name = (char *)"root";
1179 p.pw_dir = getsysdir(); 1203 else if (uid != DEFAULT_UID || (p.pw_name=get_user_name()) == NULL)
1180 }
1181 else if (uid == DEFAULT_UID && (p.pw_name=get_user_name()) != NULL) {
1182 p.pw_dir = gethomedir();
1183 }
1184 else {
1185 return NULL; 1204 return NULL;
1186 }
1187 1205
1206 p.pw_dir = gethomedir();
1188 p.pw_passwd = (char *)""; 1207 p.pw_passwd = (char *)"";
1189 p.pw_gecos = p.pw_name; 1208 p.pw_gecos = p.pw_name;
1190 p.pw_shell = NULL; 1209 p.pw_shell = NULL;
diff --git a/win32/process.c b/win32/process.c
index d78041251..0585f66a6 100644
--- a/win32/process.c
+++ b/win32/process.c
@@ -113,7 +113,7 @@ parse_interpreter(const char *cmd, interp_t *interp)
113 * See https://docs.microsoft.com/en-us/cpp/cpp/main-function-command-line-args?view=vs-2019#parsing-c-command-line-arguments 113 * See https://docs.microsoft.com/en-us/cpp/cpp/main-function-command-line-args?view=vs-2019#parsing-c-command-line-arguments
114 * (Parsing C++ Command-Line Arguments) 114 * (Parsing C++ Command-Line Arguments)
115 */ 115 */
116static char * 116char *
117quote_arg(const char *arg) 117quote_arg(const char *arg)
118{ 118{
119 int len = 0, n = 0; 119 int len = 0, n = 0;