summaryrefslogtreecommitdiffstats
path: root/os_win32/popen_win32.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'os_win32/popen_win32.cpp')
-rw-r--r--os_win32/popen_win32.cpp348
1 files changed, 348 insertions, 0 deletions
diff --git a/os_win32/popen_win32.cpp b/os_win32/popen_win32.cpp
new file mode 100644
index 0000000..6243e50
--- /dev/null
+++ b/os_win32/popen_win32.cpp
@@ -0,0 +1,348 @@
+/*
+ * os_win32/popen_win32.cpp
+ *
+ * Home page of code is: https://www.smartmontools.org
+ *
+ * Copyright (C) 2018-21 Christian Franke
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "popen.h"
+
+const char * popen_win32_cpp_cvsid = "$Id: popen_win32.cpp 5272 2021-12-18 15:55:35Z chrfranke $"
+ POPEN_H_CVSID;
+
+#include <errno.h>
+#include <fcntl.h>
+#include <io.h> // _open_osfhandle()
+#include <signal.h> // SIGSEGV
+#include <stdlib.h>
+#include <string.h>
+
+#include <windows.h>
+
+static HANDLE create_restricted_token()
+{
+ // Create SIDs for SYSTEM and Local Adminstrator
+ union {
+ SID sid;
+ char sid_space[32]; // 16
+ } adm, sys; // "S-1-5-18", "S-1-5-32-544"
+ DWORD adm_size = sizeof(adm), sys_size = sizeof(sys);
+ if (!( CreateWellKnownSid(WinBuiltinAdministratorsSid, (PSID)0, &adm.sid, &adm_size)
+ && CreateWellKnownSid(WinLocalSystemSid, (PSID)0, &sys.sid, &sys_size) )) {
+ errno = ENOMEM;
+ return (HANDLE)0;
+ }
+
+ // Open token of current process
+ HANDLE proc_token;
+ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &proc_token)) {
+ errno = EPERM;
+ return (HANDLE)0;
+ }
+
+ // Get Owner of current process: S-1-5-21-SYSTEM-GUID-USERID
+ union {
+ TOKEN_USER user;
+ char user_space[128]; // TODO: Max size?
+ } usr;
+ DWORD size = 0;
+ if (!GetTokenInformation(proc_token, TokenUser, &usr, sizeof(usr), &size)) {
+ CloseHandle(proc_token);
+ errno = EPERM;
+ return (HANDLE)0;
+ }
+
+ // Restricting token from SYSTEM or local Administrator is not effective
+ if (EqualSid(usr.user.User.Sid, &sys.sid) || EqualSid(usr.user.User.Sid, &adm.sid)) {
+ CloseHandle(proc_token);
+ errno = EINVAL;
+ return (HANDLE)0;
+ }
+
+ // The default DACL of an elevated process may not contain the user itself:
+ // D:(A;;GA;;;BA)(A;;GA;;;SY)[(A;;GXGR;;;S-1-5-5-0-LOGON_ID)]
+ // The restricted process then fails to start because the hidden
+ // console cannot be accessed. Use a standard default DACL instead:
+ // D:(A;;GA;;;S-1-5-21-SYSTEM-GUID-USERID)(A;;GA;;;BA)(A;;GA;;;SY)
+ union {
+ ACL acl;
+ char acl_space[256]; // 236
+ } dacl;
+ if (!( InitializeAcl(&dacl.acl, sizeof(dacl), ACL_REVISION)
+ && AddAccessAllowedAce(&dacl.acl, ACL_REVISION, GENERIC_ALL, usr.user.User.Sid)
+ && AddAccessAllowedAce(&dacl.acl, ACL_REVISION, GENERIC_ALL, &adm.sid)
+ && AddAccessAllowedAce(&dacl.acl, ACL_REVISION, GENERIC_ALL, &sys.sid) )) {
+ CloseHandle(proc_token);
+ errno = ENOMEM;
+ return (HANDLE)0;
+ }
+
+ // Create new token with local Administrator and most Privileges dropped
+ SID_AND_ATTRIBUTES sid_to_disable = {&adm.sid, 0};
+ HANDLE restr_token;
+ BOOL ok = CreateRestrictedToken(proc_token,
+ DISABLE_MAX_PRIVILEGE, // Keep only "SeChangeNotifyPrivilege"
+ 1, &sid_to_disable, // Disable "S-1-5-32-544" (changes group to deny only)
+ 0, (LUID_AND_ATTRIBUTES *)0, // No further privileges
+ 0, (SID_AND_ATTRIBUTES *)0, // No restricted SIDs
+ &restr_token
+ );
+ CloseHandle(proc_token);
+
+ if (!ok) {
+ errno = EPERM;
+ return (HANDLE)0;
+ }
+
+ // Set new Default DACL
+ TOKEN_DEFAULT_DACL tdacl = { &dacl.acl };
+ if (!SetTokenInformation(restr_token, TokenDefaultDacl, &tdacl, sizeof(tdacl))) {
+ CloseHandle(restr_token);
+ errno = EPERM;
+ return (HANDLE)0;
+ }
+
+ return restr_token;
+}
+
+bool popen_as_restr_check()
+{
+ HANDLE restr_token = create_restricted_token();
+ if (!restr_token)
+ return false;
+ CloseHandle(restr_token);
+ return true;
+}
+
+static FILE * s_popen_file;
+static HANDLE s_popen_process;
+
+FILE * popen_as_restr_user(const char * cmd, const char * mode, bool restricted)
+{
+ // Fail if previous run is still in progress
+ if (s_popen_file) {
+ errno = EEXIST;
+ return (FILE *)0;
+ }
+
+ // mode "w" is not implemented
+ if (!(mode[0] == 'r' && (!mode[1] || !mode[2]))) {
+ errno = EINVAL;
+ return (FILE *)0;
+ }
+
+ // Set flags for text or binary mode
+ // Note: _open_osfhandle() ignores _fmode and defaults to O_BINARY
+ int oflags; const char * fomode;
+ switch (mode[1]) {
+ case 0:
+ case 't':
+ oflags = O_RDONLY|O_TEXT;
+ fomode = "rt";
+ break;
+ case 'b':
+ oflags = O_RDONLY|O_BINARY;
+ fomode = "rb";
+ break;
+ default:
+ errno = EINVAL;
+ return (FILE *)0;
+ }
+
+ // Create stdout pipe with inheritable write end
+ HANDLE pipe_out_r, pipe_out_w;
+ if (!CreatePipe(&pipe_out_r, &pipe_out_w, (SECURITY_ATTRIBUTES *)0, 1024)) {
+ errno = EMFILE;
+ return (FILE *)0;
+ }
+ if (!SetHandleInformation(pipe_out_w, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) {
+ CloseHandle(pipe_out_r); CloseHandle(pipe_out_w);
+ errno = EMFILE;
+ return (FILE *)0;
+ }
+
+ // Connect pipe read end to new FD
+ int fd = _open_osfhandle((intptr_t)pipe_out_r, oflags);
+ if (fd < 0) {
+ CloseHandle(pipe_out_r); CloseHandle(pipe_out_w);
+ return (FILE *)0;
+ }
+
+ // Connect FD to new FILE
+ FILE * f = fdopen(fd, fomode);
+ if (!f) {
+ int err = errno;
+ close(fd); // CloseHandle(pipe_out_r)
+ CloseHandle(pipe_out_w);
+ errno = err;
+ return (FILE *)0;
+ }
+
+ // Build command line "cmd /c COMMAND"
+ int cmdlen = strlen(cmd);
+ char * shellcmd = (char *)malloc(7 + cmdlen + 1);
+ if (!shellcmd) {
+ fclose(f); // CloseHandle(pipe_out_r)
+ CloseHandle(pipe_out_w);
+ errno = ENOMEM;
+ return (FILE *)0;
+ }
+ memcpy(shellcmd, "cmd /c ", 7);
+ memcpy(shellcmd + 7, cmd, cmdlen + 1);
+
+ // Create a restricted token if requested
+ HANDLE restr_token = 0;
+ if (restricted) {
+ restr_token = create_restricted_token();
+ if (!restr_token) {
+ int err = errno;
+ fclose(f); // CloseHandle(pipe_out_r)
+ CloseHandle(pipe_out_w);
+ errno = err;
+ return (FILE *)0;
+ }
+ }
+
+ // Redirect stdin stderr to null device
+ // Don't inherit parent's stdin, script may hang if parent has no console.
+ SECURITY_ATTRIBUTES sa_inherit = { sizeof(sa_inherit), (SECURITY_DESCRIPTOR *)0, TRUE };
+ HANDLE null_in = CreateFileA("nul", GENERIC_READ , 0, &sa_inherit, OPEN_EXISTING, 0, (HANDLE)0);
+ HANDLE null_err = CreateFileA("nul", GENERIC_WRITE, 0, &sa_inherit, OPEN_EXISTING, 0, (HANDLE)0);
+
+ // Set stdio handles
+ STARTUPINFO si{}; si.cb = sizeof(si);
+ si.hStdInput = null_in;
+ si.hStdOutput = pipe_out_w;
+ si.hStdError = null_err;
+ si.dwFlags = STARTF_USESTDHANDLES;
+
+ // Create process
+ PROCESS_INFORMATION pi;
+ BOOL ok;
+ const char * shell = getenv("COMSPEC");
+ if (restr_token) {
+ ok = CreateProcessAsUserA(
+ restr_token,
+ shell, // "C:\Windows\System32\cmd.exe" or nullptr
+ shellcmd, // "cmd /c COMMAND" ("cmd" searched in PATH if COMSPEC not set)
+ (SECURITY_ATTRIBUTES *)0, (SECURITY_ATTRIBUTES *)0,
+ TRUE, // inherit
+ CREATE_NO_WINDOW, // DETACHED_PROCESS would open new console(s)
+ (void *)0, (char *)0, &si, &pi
+ );
+ }
+ else {
+ ok = CreateProcessA(
+ shell, // "C:\Windows\System32\cmd.exe" or nullptr
+ shellcmd, // "cmd /c COMMAND" ("cmd" searched in PATH if COMSPEC not set)
+ (SECURITY_ATTRIBUTES *)0, (SECURITY_ATTRIBUTES *)0,
+ TRUE, // inherit
+ CREATE_NO_WINDOW, // DETACHED_PROCESS would open new console(s)
+ (void *)0, (char *)0, &si, &pi
+ );
+ }
+ free(shellcmd);
+
+ // Close inherited handles
+ CloseHandle(null_err);
+ CloseHandle(null_in);
+ if (restr_token)
+ CloseHandle(restr_token);
+ CloseHandle(pipe_out_w);
+
+ if (!ok) {
+ fclose(f); // CloseHandle(pipe_out_r)
+ errno = ENOENT;
+ return (FILE *)0;
+ }
+
+ // Store process and FILE for pclose()
+ CloseHandle(pi.hThread);
+ s_popen_process = pi.hProcess;
+ s_popen_file = f;
+
+ return f;
+}
+
+extern "C"
+FILE * popen(const char * cmd, const char * mode)
+{
+ return popen_as_restr_user(cmd, mode, false);
+}
+
+extern "C"
+int pclose(FILE * f)
+{
+ if (f != s_popen_file) {
+ errno = EBADF;
+ return -1;
+ }
+
+ fclose(f);
+ s_popen_file = 0;
+
+ // Wait for process exitcode
+ DWORD exitcode = 42;
+ bool ok = ( WaitForSingleObject(s_popen_process, INFINITE) == WAIT_OBJECT_0
+ && GetExitCodeProcess(s_popen_process, &exitcode));
+
+ CloseHandle(s_popen_process);
+ s_popen_process = 0;
+
+ if (!ok) {
+ errno = ECHILD;
+ return -1;
+ }
+
+ // Modify exitcode for wait(3) macros
+ if (exitcode >> 23)
+ return ((exitcode << 9) >> 1) | SIGSEGV;
+ else
+ return exitcode << 8;
+}
+
+// Test program
+#ifdef TEST
+
+int main(int argc, char **argv)
+{
+ bool restricted = false;
+ int ai = 1;
+ if (argc > 1 && !strcmp(argv[ai], "-r")) {
+ restricted = true;
+ ai++;
+ }
+ if (ai + 1 != argc) {
+ printf("Usage: %s [-r] \"COMMAND ARG...\"\n", argv[0]);
+ return 1;
+ }
+ const char * cmd = argv[ai];
+
+ printf("popen_as_restr_check() = %s\n", (popen_as_restr_check() ? "true" : "false"));
+ printf("popen_as_restr_user(\"%s\", \"r\", %s):\n", cmd, (restricted ? "true" : "false"));
+ FILE * f = popen_as_restr_user(cmd, "r", restricted);
+ if (!f) {
+ perror("popen_as_restr_user");
+ return 1;
+ }
+
+ int cnt, c;
+ for (cnt = 0; (c = getc(f)) != EOF; cnt++)
+ putchar(c);
+ printf("[EOF]\nread %d bytes\n", cnt);
+
+ int status = pclose(f);
+
+ if (status == -1) {
+ perror("pclose");
+ return 1;
+ }
+ printf("pclose() = 0x%04x (exit = %d, sig = %d)\n",
+ status, WEXITSTATUS(status), WTERMSIG(status));
+ return status;
+}
+
+#endif