diff --git a/winsup/cygwin/ChangeLog b/winsup/cygwin/ChangeLog index 68334b318..f1e3b7bb7 100644 --- a/winsup/cygwin/ChangeLog +++ b/winsup/cygwin/ChangeLog @@ -1,3 +1,69 @@ +2010-04-19 Corinna Vinschen + + * autoload.cc (GetNamedPipeClientProcessId): Define. + * fhandler.h (fhandler_tty_slave::fch_open_handles): Declare private. + (fhandler_tty_slave::fch_close_handles): Ditto. + (fhandler_tty_slave::cygserver_attach_tty): Drop declaration. + (fhandler_tty_slave::fstat): Declare public. + (fhandler_tty_slave::fchmod): Declare public. + (fhandler_tty_slave::fchown): Declare public. + (class fhandler_pty_master): Add master_ctl handle. + (fhandler_pty_master::pty_master_thread): Declare public. + * fhandler_termios.cc (fhandler_termios::tcinit): If the process + is started from a non-Cygwin process, make it tty process group + leader. + * fhandler_tty.cc: Throughout accommodate additional security related + arguments in calls to functions creating or opening objects. + (close_maybe): Move to start of file to reuse it + in other methods. + (struct pipe_request): Define. + (struct pipe_reply): Define. + (fhandler_tty_slave::open): Throughout, try to open synchronization + objects with MAXIMUM_ALLOWED permissions. Drop call to cygserver. + Try to duplicate pipe handles via master_ctl pipe if duplicating + directly doesn't work. + (fhandler_tty_slave::cygserver_attach_tty): Remove. + (fhandler_tty_slave::init): Close unused incoming pipe handle. + (fhandler_pty_master::close): Send exit message to master control + thread and close master_ctl handle. + (fhandler_pty_master::pty_master_thread): New method, implementing the + master control thread. + (pty_master_thread): Static helper to start master control thread. + (fhandler_pty_master::setup): Simplify creating pipe inheritance. + Make sure we're the one creating the input_available_event. Add + comment to explain why. Create master_ctl pipe and start master + control thread. Close master_ctl handle in case of error. + * security.cc (alloc_sd): Add code to handle tty objects. Add comments + to explain what exactly is required. + (get_object_sd): New function. + (get_object_attribute): New function. + (create_object_sd_from_attribute): New function. + (set_object_sd): New function. + (set_object_attribute): New function. + (set_file_attribute): Change attribute type to mode_t. + * security.h (set_file_attribute): Change attribute type to mode_t. + (get_object_sd): Declare. + (get_object_attribute): Declare. + (create_object_sd_from_attribute): Declare. + (set_object_sd): Declare. + (set_object_attribute): Declare. + * tty.cc (tty::slave_alive): Implement directly instead of via alive. + (tty::exists): Open mutex handle with READ_CONTROL access. + (tty::alive): Remove. + (tty::open_output_mutex): Convert to inline method. + (tty::open_input_mutex): Ditto. + (tty::open_mutex): Take additional ACCESS_MASK parameter for the + mutex open access mask. + (tty::open_inuse): New method. + (tty::create_inuse): Take PSECURITY_ATTRIBUTES parameter. Drop fmt + name parameter. Always create TTY_SLAVE_ALIVE event. + (tty::get_event): Take additional PSECURITY_ATTRIBUTES parameter for + CreateEvent. + * tty.h (class tty): Change declarations according to aforementioned + changes. + (tty::open_output_mutex): Implement as inline method. + (tty::open_input_mutex): Ditto. + 2010-04-19 Corinna Vinschen * dtable.cc (dtable::init_std_file_from_handle): Set dev to diff --git a/winsup/cygwin/autoload.cc b/winsup/cygwin/autoload.cc index f5bdadeee..67a93f921 100644 --- a/winsup/cygwin/autoload.cc +++ b/winsup/cygwin/autoload.cc @@ -419,6 +419,7 @@ LoadDLLfuncEx (FindFirstVolumeA, 8, kernel32, 1) LoadDLLfuncEx (FindNextVolumeA, 12, kernel32, 1) LoadDLLfuncEx (FindVolumeClose, 4, kernel32, 1) LoadDLLfuncEx (GetConsoleWindow, 0, kernel32, 1) +LoadDLLfuncEx (GetNamedPipeClientProcessId, 8, kernel32, 1) LoadDLLfuncEx (GetSystemWindowsDirectoryW, 8, kernel32, 1) LoadDLLfuncEx (GetVolumeNameForVolumeMountPointA, 12, kernel32, 1) LoadDLLfuncEx (GetSystemDEPPolicy, 0, kernel32, 1) diff --git a/winsup/cygwin/fhandler.h b/winsup/cygwin/fhandler.h index cf9d7b9ae..1255a3fb9 100644 --- a/winsup/cygwin/fhandler.h +++ b/winsup/cygwin/fhandler.h @@ -1089,6 +1089,12 @@ class fhandler_tty_common: public fhandler_termios class fhandler_tty_slave: public fhandler_tty_common { HANDLE inuse; // used to indicate that a tty is in use + + /* Helper functions for fchmod and fchown. */ + bool fch_open_handles (); + int fch_set_sd (security_descriptor &sd, bool chown); + void fch_close_handles (); + public: /* Constructor */ fhandler_tty_slave (); @@ -1108,14 +1114,18 @@ class fhandler_tty_slave: public fhandler_tty_common void fixup_after_exec (); select_record *select_read (select_stuff *); - int cygserver_attach_tty (HANDLE*, HANDLE*); int get_unit (); virtual char const *ttyname () { return pc.dev.name; } + int __stdcall fstat (struct __stat64 *buf) __attribute__ ((regparm (2))); + int __stdcall fchmod (mode_t mode) __attribute__ ((regparm (1))); + int __stdcall fchown (__uid32_t uid, __gid32_t gid) __attribute__ ((regparm (2))); }; class fhandler_pty_master: public fhandler_tty_common { int pktmode; // non-zero if pty in a packet mode. + HANDLE master_ctl; // Control socket for handle duplication + public: int need_nl; // Next read should start with \n DWORD dwProcessId; // Owner of master handles @@ -1123,6 +1133,7 @@ public: /* Constructor */ fhandler_pty_master (); + DWORD pty_master_thread (); int process_slave_output (char *buf, size_t len, int pktmode_on); void doecho (const void *str, DWORD len); int accept_input (); diff --git a/winsup/cygwin/fhandler_termios.cc b/winsup/cygwin/fhandler_termios.cc index b5d9f2ef3..b7f7860c5 100644 --- a/winsup/cygwin/fhandler_termios.cc +++ b/winsup/cygwin/fhandler_termios.cc @@ -58,6 +58,13 @@ fhandler_termios::tcinit (tty_min *this_tc, bool force) tc->pgid = myself->pgid; tc->initialized (true); } + else if (myself->ppid == 1 && myself->pid == myself->pgid + && myself->pgid == myself->sid) + /* We have been started from a non-Cygwin process. So we just become + tty process group leader. + TODO: Investigate how SIGTTIN should be handled with pure-windows + programs. */ + tc->pgid = myself->pgid; } int diff --git a/winsup/cygwin/fhandler_tty.cc b/winsup/cygwin/fhandler_tty.cc index 30b6dda09..b95bd5f3e 100644 --- a/winsup/cygwin/fhandler_tty.cc +++ b/winsup/cygwin/fhandler_tty.cc @@ -19,12 +19,29 @@ details. */ #include "dtable.h" #include "sigproc.h" #include "pinfo.h" +#include "ntdll.h" #include "cygheap.h" #include "shared_info.h" -#include "cygserver.h" #include "cygthread.h" #include "child_info.h" +#define close_maybe(h) \ + do { \ + if (h && h != INVALID_HANDLE_VALUE) \ + CloseHandle (h); \ + } while (0) + +/* pty master control pipe messages */ +struct pipe_request { + DWORD pid; +}; + +struct pipe_reply { + HANDLE from_master; + HANDLE to_master; + DWORD error; +}; + /* tty master stuff */ fhandler_tty_master NO_COPY *tty_master; @@ -490,20 +507,20 @@ fhandler_tty_slave::open (int flags, mode_t) pty opened by fhandler_pty_master::open. In the former case, tty output is handled by a separate thread which controls output. */ shared_name (buf, OUTPUT_DONE_EVENT, get_unit ()); - output_done_event = OpenEvent (EVENT_ALL_ACCESS, TRUE, buf); + output_done_event = OpenEvent (MAXIMUM_ALLOWED, TRUE, buf); - if (!(output_mutex = get_ttyp ()->open_output_mutex ())) + if (!(output_mutex = get_ttyp ()->open_output_mutex (MAXIMUM_ALLOWED))) { errmsg = "open output mutex failed, %E"; goto err; } - if (!(input_mutex = get_ttyp ()->open_input_mutex ())) + if (!(input_mutex = get_ttyp ()->open_input_mutex (MAXIMUM_ALLOWED))) { errmsg = "open input mutex failed, %E"; goto err; } shared_name (buf, INPUT_AVAILABLE_EVENT, get_unit ()); - if (!(input_available_event = OpenEvent (EVENT_ALL_ACCESS, TRUE, buf))) + if (!(input_available_event = OpenEvent (MAXIMUM_ALLOWED, TRUE, buf))) { errmsg = "open input event failed, %E"; goto err; @@ -512,14 +529,23 @@ fhandler_tty_slave::open (int flags, mode_t) /* The ioctl events may or may not exist. See output_done_event, above. */ shared_name (buf, IOCTL_REQUEST_EVENT, get_unit ()); - ioctl_request_event = OpenEvent (EVENT_ALL_ACCESS, TRUE, buf); + ioctl_request_event = OpenEvent (MAXIMUM_ALLOWED, TRUE, buf); shared_name (buf, IOCTL_DONE_EVENT, get_unit ()); - ioctl_done_event = OpenEvent (EVENT_ALL_ACCESS, TRUE, buf); + ioctl_done_event = OpenEvent (MAXIMUM_ALLOWED, TRUE, buf); /* FIXME: Needs a method to eliminate tty races */ { + /* Create security attribute. Default permissions are 0620. */ + security_descriptor sd; + sd.malloc (sizeof (SECURITY_DESCRIPTOR)); + InitializeSecurityDescriptor (sd, SECURITY_DESCRIPTOR_REVISION); + SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE }; + if (!create_object_sd_from_attribute (NULL, myself->uid, myself->gid, + S_IFCHR | S_IRUSR | S_IWUSR | S_IWGRP, + sd)) + sa.lpSecurityDescriptor = (PSECURITY_DESCRIPTOR) sd; acquire_output_mutex (500); - inuse = get_ttyp ()->create_inuse (TTY_SLAVE_ALIVE); + inuse = get_ttyp ()->create_inuse (&sa); get_ttyp ()->was_opened = true; release_output_mutex (); } @@ -531,43 +557,40 @@ fhandler_tty_slave::open (int flags, mode_t) goto err_no_errno; } - if (myself->pid == get_ttyp ()->master_pid - || cygserver_running == CYGSERVER_UNAVAIL - || !cygserver_attach_tty (&from_master_local, &to_master_local)) + if (get_ttyp ()->master_pid < 0) + { + errmsg = "*** master is closed"; + set_errno (EAGAIN); + goto err_no_errno; + } + /* Three case for duplicating the pipe handles: + - Either we're the master. In this case, just duplicate the handles. + - Or, we have the right to open the master process for handle duplication. + In this case, just duplicate the handles. + - Or, we have to ask the master process itself. In this case, send our + pid to the master process and check the reply. The reply contains + either the handles, or an error code which tells us why we didn't + get the handles. */ + if (myself->pid == get_ttyp ()->master_pid) + { + /* This is the most common case, just calling openpty. */ + termios_printf ("dup handles within myself."); + tty_owner = GetCurrentProcess (); + } + else { - if (get_ttyp ()->master_pid < 0) - { - errmsg = "*** master is closed"; - set_errno (EAGAIN); - goto err_no_errno; - } pinfo p (get_ttyp ()->master_pid); if (!p) - { - errmsg = "*** couldn't find tty master"; - set_errno (EAGAIN); - goto err_no_errno; - } - HANDLE tty_owner; - if (myself->pid == get_ttyp ()->master_pid) - { - /* This is the most common case, just calling openpty. */ - termios_printf ("dup handles within myself."); - tty_owner = GetCurrentProcess (); - } + termios_printf ("*** couldn't find tty master"); else { - termios_printf ("cannot dup handles via server. using old method."); tty_owner = OpenProcess (PROCESS_DUP_HANDLE, FALSE, p->dwProcessId); - if (tty_owner == NULL) - { - termios_printf ("can't open tty (%d) handle process %d", - get_unit (), get_ttyp ()->master_pid); - __seterrno (); - goto err_no_msg; - } + if (tty_owner) + termios_printf ("dup handles directly since I'm allmighty."); } - + } + if (tty_owner) + { if (!DuplicateHandle (tty_owner, get_ttyp ()->from_master, GetCurrentProcess (), &from_master_local, 0, TRUE, DUPLICATE_SAME_ACCESS)) @@ -577,8 +600,6 @@ fhandler_tty_slave::open (int flags, mode_t) __seterrno (); goto err_no_msg; } - - VerifyHandle (from_master_local); if (!DuplicateHandle (tty_owner, get_ttyp ()->to_master, GetCurrentProcess (), &to_master_local, 0, TRUE, DUPLICATE_SAME_ACCESS)) @@ -586,10 +607,35 @@ fhandler_tty_slave::open (int flags, mode_t) errmsg = "can't duplicate output, %E"; goto err; } - VerifyHandle (to_master_local); if (tty_owner != GetCurrentProcess ()) CloseHandle (tty_owner); } + else + { + pipe_request req = { GetCurrentProcessId () }; + pipe_reply repl; + DWORD len; + + __small_sprintf (buf, "\\\\.\\pipe\\cygwin-%S-tty%d-master-ctl", + &installation_key, get_unit ()); + termios_printf ("dup handles via master control pipe %s", buf); + if (!CallNamedPipe (buf, &req, sizeof req, &repl, sizeof repl, + &len, 500)) + { + errmsg = "can't call master, %E"; + goto err; + } + from_master_local = repl.from_master; + to_master_local = repl.to_master; + if (!from_master_local || !to_master_local) + { + SetLastError (repl.error); + errmsg = "error duplicating pipes, %E"; + goto err; + } + } + VerifyHandle (from_master_local); + VerifyHandle (to_master_local); termios_printf ("duplicated from_master %p->%p from tty_owner", get_ttyp ()->from_master, from_master_local); @@ -660,31 +706,7 @@ fhandler_tty_slave::close () } int -fhandler_tty_slave::cygserver_attach_tty (LPHANDLE from_master_ptr, - LPHANDLE to_master_ptr) -{ - if (!from_master_ptr || !to_master_ptr) - return 0; - - pinfo p (get_ttyp ()->master_pid); - if (!p) - return 0; - - client_request_attach_tty req (p->dwProcessId, - (HANDLE) get_ttyp ()->from_master, - (HANDLE) get_ttyp ()->to_master); - - if (req.make_request () == -1 || req.error_code ()) - return 0; - - *from_master_ptr = req.from_master (); - *to_master_ptr = req.to_master (); - - return 1; -} - -int -fhandler_tty_slave::init (HANDLE, DWORD a, mode_t) +fhandler_tty_slave::init (HANDLE f, DWORD a, mode_t) { int flags = 0; @@ -696,7 +718,12 @@ fhandler_tty_slave::init (HANDLE, DWORD a, mode_t) if (a == (GENERIC_READ | GENERIC_WRITE)) flags = O_RDWR; - return open (flags); + int ret = open (flags); + + if (f != INVALID_HANDLE_VALUE) + CloseHandle (f); /* Reopened by open */ + + return ret; } ssize_t __stdcall @@ -1117,6 +1144,177 @@ out: return retval; } +int __stdcall +fhandler_tty_slave::fstat (struct __stat64 *st) +{ + fhandler_base::fstat (st); + + bool to_close = false; + if (!input_available_event) + { + char buf[MAX_PATH]; + shared_name (buf, INPUT_AVAILABLE_EVENT, get_unit ()); + input_available_event = OpenEvent (READ_CONTROL, TRUE, buf); + if (input_available_event) + to_close = true; + } + if (!input_available_event + || get_object_attribute (input_available_event, &st->st_uid, &st->st_gid, + &st->st_mode)) + { + /* If we can't access the ACL, or if the tty doesn't actually exist, + then fake uid and gid to strict, system-like values. */ + st->st_mode = S_IFCHR | S_IRUSR | S_IWUSR; + st->st_uid = 18; + st->st_gid = 544; + } + if (to_close) + CloseHandle (input_available_event); + return 0; +} + +/* Helper function for fchmod and fchown, which just opens all handles + and signals success via bool return. */ +bool +fhandler_tty_slave::fch_open_handles () +{ + char buf[MAX_PATH]; + + tc = cygwin_shared->tty[get_unit ()]; + shared_name (buf, INPUT_AVAILABLE_EVENT, get_unit ()); + input_available_event = OpenEvent (READ_CONTROL | WRITE_DAC | WRITE_OWNER, + TRUE, buf); + output_mutex = get_ttyp ()->open_output_mutex (WRITE_DAC | WRITE_OWNER); + input_mutex = get_ttyp ()->open_input_mutex (WRITE_DAC | WRITE_OWNER); + inuse = get_ttyp ()->open_inuse (WRITE_DAC | WRITE_OWNER); + if (!input_available_event || !output_mutex || !input_mutex || !inuse) + { + __seterrno (); + return false; + } + /* These members are optional, no error checking */ + shared_name (buf, OUTPUT_DONE_EVENT, get_unit ()); + output_done_event = OpenEvent (WRITE_DAC | WRITE_OWNER, TRUE, buf); + shared_name (buf, IOCTL_REQUEST_EVENT, get_unit ()); + ioctl_request_event = OpenEvent (WRITE_DAC | WRITE_OWNER, TRUE, buf); + shared_name (buf, IOCTL_DONE_EVENT, get_unit ()); + ioctl_done_event = OpenEvent (WRITE_DAC | WRITE_OWNER, TRUE, buf); + return true; +} + +/* Helper function for fchmod and fchown, which sets the new security + descriptor on all objects representing the tty. */ +int +fhandler_tty_slave::fch_set_sd (security_descriptor &sd, bool chown) +{ + security_descriptor sd_old; + + get_object_sd (input_available_event, sd_old); + if (/*!set_object_sd (get_io_handle (), sd, chown) + && !set_object_sd (get_output_handle (), sd, chown) + && */ !set_object_sd (input_available_event, sd, chown) + && !set_object_sd (output_mutex, sd, chown) + && !set_object_sd (input_mutex, sd, chown) + && !set_object_sd (inuse, sd, chown) + && (!output_done_event + || !set_object_sd (output_done_event, sd, chown)) + && (!ioctl_request_event + || !set_object_sd (ioctl_request_event, sd, chown)) + && (!ioctl_done_event + || !set_object_sd (ioctl_done_event, sd, chown))) + return 0; + /*set_object_sd (get_io_handle (), sd_old, chown); + set_object_sd (get_output_handle (), sd_old, chown);*/ + set_object_sd (input_available_event, sd_old, chown); + set_object_sd (output_mutex, sd_old, chown); + set_object_sd (input_mutex, sd_old, chown); + set_object_sd (inuse, sd_old, chown); + if (!output_done_event) + set_object_sd (output_done_event, sd_old, chown); + if (!ioctl_request_event) + set_object_sd (ioctl_request_event, sd_old, chown); + if (!ioctl_done_event) + set_object_sd (ioctl_done_event, sd_old, chown); + return -1; +} + +/* Helper function for fchmod and fchown, which closes all object handles in + the tty. */ +void +fhandler_tty_slave::fch_close_handles () +{ + close_maybe (get_io_handle ()); + close_maybe (get_output_handle ()); + close_maybe (output_done_event); + close_maybe (ioctl_done_event); + close_maybe (ioctl_request_event); + close_maybe (input_available_event); + close_maybe (output_mutex); + close_maybe (input_mutex); + close_maybe (inuse); +} + +int __stdcall +fhandler_tty_slave::fchmod (mode_t mode) +{ + int ret = -1; + bool to_close = false; + security_descriptor sd; + __uid32_t uid; + __gid32_t gid; + + if (!input_available_event) + { + to_close = true; + if (!fch_open_handles ()) + goto errout; + } + sd.malloc (sizeof (SECURITY_DESCRIPTOR)); + InitializeSecurityDescriptor (sd, SECURITY_DESCRIPTOR_REVISION); + if (!get_object_attribute (input_available_event, &uid, &gid, NULL) + && !create_object_sd_from_attribute (NULL, uid, gid, S_IFCHR | mode, sd)) + ret = fch_set_sd (sd, false); +errout: + if (to_close) + fch_close_handles (); + return ret; +} + +int __stdcall +fhandler_tty_slave::fchown (__uid32_t uid, __gid32_t gid) +{ + int ret = -1; + bool to_close = false; + mode_t mode; + __uid32_t o_uid; + __gid32_t o_gid; + security_descriptor sd; + + if (uid == ILLEGAL_UID && gid == ILLEGAL_GID) + return 0; + if (!input_available_event) + { + to_close = true; + if (!fch_open_handles ()) + goto errout; + } + sd.malloc (sizeof (SECURITY_DESCRIPTOR)); + InitializeSecurityDescriptor (sd, SECURITY_DESCRIPTOR_REVISION); + if (!get_object_attribute (input_available_event, &o_uid, &o_gid, &mode)) + { + if ((uid == ILLEGAL_UID || uid == o_uid) + && (gid == ILLEGAL_GID || gid == o_gid)) + ret = 0; + else if (!create_object_sd_from_attribute (input_available_event, + uid, gid, S_IFCHR | mode, sd)) + ret = fch_set_sd (sd, true); + } +errout: + if (to_close) + fch_close_handles (); + return ret; +} + /******************************************************* fhandler_pty_master */ @@ -1215,6 +1413,18 @@ fhandler_pty_master::close () arch->from_master, arch->to_master, arch->dwProcessId); if (cygwin_finished_initializing) { + if (arch->master_ctl && get_ttyp ()->master_pid == myself->pid) + { + char buf[MAX_PATH]; + pipe_request req = { (DWORD) -1 }; + pipe_reply repl; + DWORD len; + + __small_sprintf (buf, "\\\\.\\pipe\\cygwin-%S-tty%d-master-ctl", + &installation_key, get_unit ()); + CallNamedPipe (buf, &req, sizeof req, &repl, sizeof repl, &len, 500); + CloseHandle (arch->master_ctl); + } if (!ForceCloseHandle (arch->from_master)) termios_printf ("error closing from_master %p, %E", arch->from_master); if (!ForceCloseHandle (arch->to_master)) @@ -1361,16 +1571,154 @@ fhandler_tty_master::init_console () return 0; } -#define close_maybe(h) \ - do { \ - if (h && h != INVALID_HANDLE_VALUE) \ - CloseHandle (h); \ - } while (0) +extern "C" BOOL WINAPI GetNamedPipeClientProcessId (HANDLE, PULONG); + +/* This thread function handles the master control pipe. It waits for a + client to connect. Then it checks if the client process has permissions + to access the tty handles. If so, it opens the client process and + duplicates the handles into that process. If that fails, it sends a reply + with at least one handle set to NULL and an error code. Last but not + least, the client is disconnected and the thread waits for the next client. + + A special case is when the master side of the tty is about to be closed. + The client side is the fhandler_pty_master::close function and it sends + a PID -1 in that case. On Vista and later a check is performed that the + request to leave really comes from the master process itself. On earlier + OSes there's no function to check for the PID of the client process so + we have to trust the client side. + + Since there's + always only one pipe instance, there's a chance that clients have to + wait to connect to the master control pipe. Therefore the client calls + to CallNamedPipe should have a big enough timeout value. For now this + is 500ms. Hope that's enough. */ + +DWORD +fhandler_pty_master::pty_master_thread () +{ + bool exit = false; + GENERIC_MAPPING map = { EVENT_QUERY_STATE, EVENT_MODIFY_STATE, 0, + EVENT_QUERY_STATE | EVENT_MODIFY_STATE }; + pipe_request req; + DWORD len; + security_descriptor sd; + HANDLE token; + PRIVILEGE_SET ps; + BOOL ret; + DWORD pid; + + termios_printf ("Entered"); + while (!exit && ConnectNamedPipe (master_ctl, NULL)) + { + pipe_reply repl = { NULL, NULL, 0 }; + bool deimp = false; + BOOL allow = FALSE; + ACCESS_MASK access = EVENT_MODIFY_STATE; + HANDLE client = NULL; + + if (!ReadFile (master_ctl, &req, sizeof req, &len, NULL)) + { + termios_printf ("ReadFile, %E"); + goto reply; + } + /* This function is only available since Vista, unfortunately. + In earlier OSes we simply have to believe that the client + has no malicious intent (== sends arbitrary PIDs). */ + if (!GetNamedPipeClientProcessId (master_ctl, &pid)) + pid = req.pid; + if (get_object_sd (input_available_event, sd)) + { + termios_printf ("get_object_sd, %E"); + goto reply; + } + cygheap->user.deimpersonate (); + deimp = true; + if (!ImpersonateNamedPipeClient (master_ctl)) + { + termios_printf ("ImpersonateNamedPipeClient, %E"); + goto reply; + } + if (!OpenThreadToken (GetCurrentThread (), TOKEN_QUERY, TRUE, &token)) + { + termios_printf ("OpenThreadToken, %E"); + goto reply; + } + len = sizeof ps; + ret = AccessCheck (sd, token, access, &map, &ps, &len, &access, &allow); + CloseHandle (token); + if (!ret) + { + termios_printf ("AccessCheck, %E"); + goto reply; + } + if (!RevertToSelf ()) + { + termios_printf ("RevertToSelf, %E"); + goto reply; + } + if (req.pid == (DWORD) -1) /* Request to finish thread. */ + { + /* Pre-Vista: Just believe in the good of the client process. + Post-Vista: Check if the requesting process is the master + process itself. */ + if (pid == (DWORD) -1 || pid == GetCurrentProcessId ()) + exit = true; + goto reply; + } + if (allow) + { + client = OpenProcess (PROCESS_DUP_HANDLE, FALSE, pid); + if (!client) + { + termios_printf ("OpenProcess, %E"); + goto reply; + } + if (!DuplicateHandle (GetCurrentProcess (), from_master, + client, &repl.from_master, + 0, TRUE, DUPLICATE_SAME_ACCESS)) + { + termios_printf ("DuplicateHandle (from_master), %E"); + goto reply; + } + if (!DuplicateHandle (GetCurrentProcess (), to_master, + client, &repl.to_master, + 0, TRUE, DUPLICATE_SAME_ACCESS)) + { + termios_printf ("DuplicateHandle (to_master), %E"); + goto reply; + } + } +reply: + repl.error = GetLastError (); + if (client) + CloseHandle (client); + if (deimp) + cygheap->user.reimpersonate (); + sd.free (); + termios_printf ("Reply: from %p, to %p, error %lu", + repl.from_master, repl.to_master, repl.error ); + if (!WriteFile (master_ctl, &repl, sizeof repl, &len, NULL)) + termios_printf ("WriteFile, %E"); + if (!DisconnectNamedPipe (master_ctl)) + termios_printf ("DisconnectNamedPipe, %E"); + } + termios_printf ("Leaving"); + return 0; +} + +static DWORD WINAPI +pty_master_thread (VOID *arg) +{ + return ((fhandler_pty_master *) arg)->pty_master_thread (); +} bool fhandler_pty_master::setup (bool ispty) { int res; + security_descriptor sd; + SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE }; + tty& t = *cygwin_shared->tty[get_unit ()]; tcinit (&t, true); /* Set termios information. Force initialization. */ @@ -1379,78 +1727,97 @@ fhandler_pty_master::setup (bool ispty) DWORD pipe_mode = PIPE_NOWAIT; /* Create communication pipes */ - char pipename[sizeof("ttyNNNN-from-master")]; __small_sprintf (pipename, "tty%d-from-master", get_unit ()); - res = fhandler_pipe::create_selectable (&sec_none_nih, from_master, - get_output_handle (), 128 * 1024, - pipename); + res = fhandler_pipe::create_selectable (ispty ? &sec_none : &sec_none_nih, + from_master, get_output_handle (), + 128 * 1024, pipename); if (res) { errstr = "input pipe"; goto err; } - /* Only ptys should create inheritable handles by default. ttys are - parcelled out on an as-needed basis and handle inheritance differently. */ - if (ispty && !SetHandleInformation (get_output_handle (), HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) - { - errstr = "inheritable get_output_handle ()"; - goto err; - } if (!SetNamedPipeHandleState (get_output_handle (), &pipe_mode, NULL, NULL)) termios_printf ("can't set output_handle(%p) to non-blocking mode", get_output_handle ()); __small_sprintf (pipename, "tty%d-to-master", get_unit ()); - res = fhandler_pipe::create_selectable (&sec_none_nih, get_io_handle (), - to_master, 128 * 1024, pipename); + res = fhandler_pipe::create_selectable (ispty ? &sec_none : &sec_none_nih, + get_io_handle (), to_master, + 128 * 1024, pipename); if (res) { errstr = "output pipe"; goto err; } - /* Only ptys should create inheritable handles by default. ttys are - parcelled out on an as-needed basis and handle inheritance differently. */ - if (ispty && !SetHandleInformation (get_io_handle (), HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) - { - errstr = "inheritable get_io_handle ()"; - goto err; - } need_nl = 0; - /* We do not allow others to open us (for handle duplication) - but rely on cygheap->inherited_ctty for descendant processes. - In the future the cygserver may allow access by others. */ - - if (cygserver_running == CYGSERVER_UNKNOWN) - cygserver_init (); + /* Create security attribute. Default permissions are 0620. */ + sd.malloc (sizeof (SECURITY_DESCRIPTOR)); + InitializeSecurityDescriptor (sd, SECURITY_DESCRIPTOR_REVISION); + if (!create_object_sd_from_attribute (NULL, myself->uid, myself->gid, + S_IFCHR | S_IRUSR | S_IWUSR | S_IWGRP, + sd)) + sa.lpSecurityDescriptor = (PSECURITY_DESCRIPTOR) sd; /* Create synchronisation events */ if (!ispty) { - if (!(output_done_event = t.get_event (errstr = OUTPUT_DONE_EVENT))) + if (!(output_done_event = t.get_event (errstr = OUTPUT_DONE_EVENT, &sa))) goto err; - if (!(ioctl_done_event = t.get_event (errstr = IOCTL_DONE_EVENT))) + if (!(ioctl_done_event = t.get_event (errstr = IOCTL_DONE_EVENT, &sa))) goto err; - if (!(ioctl_request_event = t.get_event (errstr = IOCTL_REQUEST_EVENT))) + if (!(ioctl_request_event = t.get_event (errstr = IOCTL_REQUEST_EVENT, + &sa))) goto err; } - if (!(input_available_event = t.get_event (errstr = INPUT_AVAILABLE_EVENT, TRUE))) + /* Carefully check that the input_available_event didn't already exist. + This is a measure to make sure that the event security descriptor + isn't occupied by a malicious process. We must make sure that the + event's security descriptor is what we expect it to be. */ + if (!(input_available_event = t.get_event (errstr = INPUT_AVAILABLE_EVENT, + &sa, TRUE)) + || GetLastError () == ERROR_ALREADY_EXISTS) goto err; char buf[MAX_PATH]; errstr = shared_name (buf, OUTPUT_MUTEX, t.ntty); - if (!(output_mutex = CreateMutex (&sec_all, FALSE, buf))) + if (!(output_mutex = CreateMutex (&sa, FALSE, buf))) goto err; errstr = shared_name (buf, INPUT_MUTEX, t.ntty); - if (!(input_mutex = CreateMutex (&sec_all, FALSE, buf))) + if (!(input_mutex = CreateMutex (&sa, FALSE, buf))) goto err; + if (ispty) + { + /* Create master control pipe which allows the master to duplicate + the pty pipe handles to processes which deserve it. */ + cygthread *h; + __small_sprintf (buf, "\\\\.\\pipe\\cygwin-%S-tty%d-master-ctl", + &installation_key, get_unit ()); + master_ctl = CreateNamedPipe (buf, PIPE_ACCESS_DUPLEX, + PIPE_WAIT | PIPE_TYPE_MESSAGE + | PIPE_READMODE_MESSAGE, 1, 4096, 4096, + 0, &sec_all_nih); + if (master_ctl == INVALID_HANDLE_VALUE) + { + errstr = "pty master control pipe"; + goto err; + } + h = new cygthread (::pty_master_thread, 0, this, "pty_master"); + if (!h) + { + errstr = "pty master control thread"; + goto err; + } + h->zap_h (); + } + t.from_master = from_master; t.to_master = to_master; // /* screws up tty master */ ProtectHandle1INH (output_mutex, output_mutex); @@ -1475,6 +1842,7 @@ err: close_maybe (input_mutex); close_maybe (from_master); close_maybe (to_master); + close_maybe (master_ctl); termios_printf ("tty%d open failed - failed to create %s", errstr); return false; } diff --git a/winsup/cygwin/security.cc b/winsup/cygwin/security.cc index b09a719f3..c180197e1 100644 --- a/winsup/cygwin/security.cc +++ b/winsup/cygwin/security.cc @@ -1,7 +1,7 @@ /* security.cc: NT file access control functions Copyright 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, - 2006, 2007, 2008, 2009 Red Hat, Inc. + 2006, 2007, 2008, 2009, 2010 Red Hat, Inc. Originaly written by Gunther Ebert, gunther.ebert@ixos-leipzig.de Completely rewritten by Corinna Vinschen @@ -464,6 +464,10 @@ alloc_sd (path_conv &pc, __uid32_t uid, __gid32_t gid, int attribute, /* From here fill ACL. */ size_t acl_len = sizeof (ACL); int ace_off = 0; + /* Only used for sync objects (for ttys). The admins group should + always have the right to manipulate the ACL, so we have to make sure + that the ACL gives the admins group STANDARD_RIGHTS_ALL access. */ + bool saw_admins = false; /* Construct allow attribute for owner. Don't set FILE_READ/WRITE_ATTRIBUTES unconditionally on Samba, otherwise @@ -480,9 +484,12 @@ alloc_sd (path_conv &pc, __uid32_t uid, __gid32_t gid, int attribute, if (S_ISDIR (attribute) && (attribute & (S_IWUSR | S_IXUSR)) == (S_IWUSR | S_IXUSR)) owner_allow |= FILE_DELETE_CHILD; + /* For sync objects note that the owner is admin. */ + if (S_ISCHR (attribute) && owner_sid == well_known_admins_sid) + saw_admins = true; /* Construct allow attribute for group. */ - DWORD group_allow = STANDARD_RIGHTS_READ + DWORD group_allow = STANDARD_RIGHTS_READ | SYNCHRONIZE | (pc.fs_is_samba () ? 0 : FILE_READ_ATTRIBUTES); if (attribute & S_IRGRP) group_allow |= FILE_GENERIC_READ; @@ -494,9 +501,15 @@ alloc_sd (path_conv &pc, __uid32_t uid, __gid32_t gid, int attribute, && (attribute & (S_IWGRP | S_IXGRP)) == (S_IWGRP | S_IXGRP) && !(attribute & S_ISVTX)) group_allow |= FILE_DELETE_CHILD; + /* For sync objects, add STANDARD_RIGHTS_ALL for admins group. */ + if (S_ISCHR (attribute) && group_sid == well_known_admins_sid) + { + group_allow |= STANDARD_RIGHTS_ALL; + saw_admins = true; + } /* Construct allow attribute for everyone. */ - DWORD other_allow = STANDARD_RIGHTS_READ + DWORD other_allow = STANDARD_RIGHTS_READ | SYNCHRONIZE | (pc.fs_is_samba () ? 0 : FILE_READ_ATTRIBUTES); if (attribute & S_IROTH) other_allow |= FILE_GENERIC_READ; @@ -560,6 +573,17 @@ alloc_sd (path_conv &pc, __uid32_t uid, __gid32_t gid, int attribute, group_sid, acl_len, NO_INHERITANCE)) return NULL; + /* For sync objects, if we didn't see the admins group so far, add entry + with STANDARD_RIGHTS_ALL access. */ + if (S_ISCHR (attribute) && !saw_admins) + { + if (!add_access_allowed_ace (acl, ace_off++, STANDARD_RIGHTS_ALL, + well_known_admins_sid, acl_len, + NO_INHERITANCE)) + return NULL; + saw_admins = true; + } + /* Set allow ACE for everyone. */ if (!add_access_allowed_ace (acl, ace_off++, other_allow, well_known_world_sid, acl_len, NO_INHERITANCE)) @@ -582,7 +606,8 @@ alloc_sd (path_conv &pc, __uid32_t uid, __gid32_t gid, int attribute, cygpsid ace_sid ((PSID) &ace->SidStart); /* Check for related ACEs. */ - if (ace_sid == well_known_null_sid) + if (ace_sid == well_known_null_sid + || (S_ISCHR (attribute) && ace_sid == well_known_admins_sid)) continue; if ((ace_sid == cur_owner_sid) || (ace_sid == owner_sid) @@ -716,9 +741,86 @@ set_security_attribute (path_conv &pc, int attribute, PSECURITY_ATTRIBUTES psa, attribute, sd); } +int +get_object_sd (HANDLE handle, security_descriptor &sd) +{ + ULONG len = 0; + NTSTATUS status; + + status = NtQuerySecurityObject (handle, ALL_SECURITY_INFORMATION, + sd, len, &len); + if (status != STATUS_BUFFER_TOO_SMALL) + { + __seterrno_from_nt_status (status); + return -1; + } + if (!sd.malloc (len)) + { + set_errno (ENOMEM); + return -1; + } + status = NtQuerySecurityObject (handle, ALL_SECURITY_INFORMATION, + sd, len, &len); + if (!NT_SUCCESS (status)) + { + __seterrno_from_nt_status (status); + return -1; + } + return 0; +} + +int +get_object_attribute (HANDLE handle, __uid32_t *uidret, __gid32_t *gidret, + mode_t *attribute) +{ + security_descriptor sd; + + if (get_object_sd (handle, sd)) + return -1; + get_info_from_sd (sd, attribute, uidret, gidret); + return 0; +} + +int +create_object_sd_from_attribute (HANDLE handle, __uid32_t uid, __gid32_t gid, + mode_t attribute, security_descriptor &sd) +{ + path_conv pc; + if ((handle && get_object_sd (handle, sd)) + || !alloc_sd (pc, uid, gid, attribute, sd)) + return -1; + return 0; +} + +int +set_object_sd (HANDLE handle, security_descriptor &sd, bool chown) +{ + NTSTATUS status; + status = NtSetSecurityObject (handle, chown ? ALL_SECURITY_INFORMATION + : DACL_SECURITY_INFORMATION, sd); + if (!NT_SUCCESS (status)) + { + __seterrno_from_nt_status (status); + return -1; + } + return 0; +} + +int +set_object_attribute (HANDLE handle, __uid32_t uid, __gid32_t gid, + mode_t attribute) +{ + security_descriptor sd; + + if (create_object_sd_from_attribute (handle, uid, gid, attribute, sd) + || set_object_sd (handle, sd, uid != ILLEGAL_UID || gid != ILLEGAL_GID)) + return -1; + return 0; +} + int set_file_attribute (HANDLE handle, path_conv &pc, - __uid32_t uid, __gid32_t gid, int attribute) + __uid32_t uid, __gid32_t gid, mode_t attribute) { int ret = -1; diff --git a/winsup/cygwin/security.h b/winsup/cygwin/security.h index dcc080f2f..dde4d214e 100644 --- a/winsup/cygwin/security.h +++ b/winsup/cygwin/security.h @@ -350,7 +350,14 @@ class path_conv; int __stdcall get_file_attribute (HANDLE, path_conv &, mode_t *, __uid32_t *, __gid32_t *); int __stdcall set_file_attribute (HANDLE, path_conv &, - __uid32_t, __gid32_t, int); + __uid32_t, __gid32_t, mode_t); +int __stdcall get_object_sd (HANDLE, security_descriptor &); +int __stdcall get_object_attribute (HANDLE, __uid32_t *, __gid32_t *, mode_t *); +int __stdcall set_object_attribute (HANDLE, __uid32_t, __gid32_t, mode_t); +int __stdcall create_object_sd_from_attribute (HANDLE, __uid32_t, __gid32_t, + mode_t, security_descriptor &); +int __stdcall set_object_sd (HANDLE, security_descriptor &, bool); + int __stdcall get_reg_attribute (HKEY hkey, mode_t *, __uid32_t *, __gid32_t *); LONG __stdcall get_file_sd (HANDLE fh, path_conv &, security_descriptor &sd); LONG __stdcall set_file_sd (HANDLE fh, path_conv &, security_descriptor &sd, diff --git a/winsup/cygwin/tty.cc b/winsup/cygwin/tty.cc index e7018106d..0991cd66d 100644 --- a/winsup/cygwin/tty.cc +++ b/winsup/cygwin/tty.cc @@ -1,7 +1,7 @@ /* tty.cc - Copyright 1997, 1998, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2008, 2009 - Red Hat, Inc. + Copyright 1997, 1998, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2008, 2009, + 2010 Red Hat, Inc. This file is part of Cygwin. @@ -286,12 +286,6 @@ out: return freetty; } -bool -tty::slave_alive () -{ - return alive (TTY_SLAVE_ALIVE); -} - bool tty::exists () { @@ -307,7 +301,7 @@ tty::exists () CloseHandle (r); CloseHandle (w); - HANDLE h = open_output_mutex (); + HANDLE h = open_output_mutex (READ_CONTROL); if (h) { CloseHandle (h); @@ -317,45 +311,38 @@ tty::exists () } bool -tty::alive (const char *fmt) +tty::slave_alive () { HANDLE ev; - char buf[MAX_PATH]; - - shared_name (buf, fmt, ntty); - if ((ev = OpenEvent (EVENT_ALL_ACCESS, FALSE, buf))) + if ((ev = open_inuse (READ_CONTROL))) CloseHandle (ev); return ev != NULL; } HANDLE -tty::open_output_mutex () -{ - return open_mutex (OUTPUT_MUTEX); -} - -HANDLE -tty::open_input_mutex () -{ - return open_mutex (INPUT_MUTEX); -} - -HANDLE -tty::open_mutex (const char *mutex) +tty::open_mutex (const char *mutex, ACCESS_MASK access) { char buf[MAX_PATH]; shared_name (buf, mutex, ntty); - return OpenMutex (MUTEX_ALL_ACCESS, TRUE, buf); + return OpenMutex (access, TRUE, buf); } HANDLE -tty::create_inuse (const char *fmt) +tty::open_inuse (ACCESS_MASK access) +{ + char buf[MAX_PATH]; + shared_name (buf, TTY_SLAVE_ALIVE, ntty); + return OpenEvent (access, FALSE, buf); +} + +HANDLE +tty::create_inuse (PSECURITY_ATTRIBUTES sa) { HANDLE h; char buf[MAX_PATH]; - shared_name (buf, fmt, ntty); - h = CreateEvent (&sec_all, TRUE, FALSE, buf); + shared_name (buf, TTY_SLAVE_ALIVE, ntty); + h = CreateEvent (sa, TRUE, FALSE, buf); termios_printf ("%s %p", buf, h); if (!h) termios_printf ("couldn't open inuse event, %E", buf); @@ -374,13 +361,15 @@ tty::init () } HANDLE -tty::get_event (const char *fmt, BOOL manual_reset) +tty::get_event (const char *fmt, PSECURITY_ATTRIBUTES sa, BOOL manual_reset) { HANDLE hev; char buf[MAX_PATH]; shared_name (buf, fmt, ntty); - if (!(hev = CreateEvent (&sec_all, manual_reset, FALSE, buf))) + if (!sa) + sa = &sec_all; + if (!(hev = CreateEvent (sa, manual_reset, FALSE, buf))) { termios_printf ("couldn't create %s", buf); set_errno (ENOENT); /* FIXME this can't be the right errno */ diff --git a/winsup/cygwin/tty.h b/winsup/cygwin/tty.h index b4c02a411..630bdb048 100644 --- a/winsup/cygwin/tty.h +++ b/winsup/cygwin/tty.h @@ -1,6 +1,6 @@ /* tty.h: shared tty info for cygwin - Copyright 2000, 2001, 2002, 2003, 2004, 2006 Red Hat, Inc. + Copyright 2000, 2001, 2002, 2003, 2004, 2006, 2009, 2010 Red Hat, Inc. This file is part of Cygwin. @@ -84,7 +84,8 @@ class fhandler_pty_master; class tty: public tty_min { - HANDLE get_event (const char *fmt, BOOL manual_reset = FALSE) + HANDLE get_event (const char *fmt, PSECURITY_ATTRIBUTES sa, + BOOL manual_reset = FALSE); __attribute__ ((regparm (3))); public: pid_t master_pid; /* PID of tty master process */ @@ -95,13 +96,14 @@ public: bool was_opened; /* True if opened at least once. */ void init (); - HANDLE create_inuse (const char *); - bool alive (const char *fmt); + HANDLE open_inuse (ACCESS_MASK access); + HANDLE create_inuse (PSECURITY_ATTRIBUTES); bool slave_alive (); - bool master_alive (); - HANDLE open_mutex (const char *mutex); - HANDLE open_output_mutex (); - HANDLE open_input_mutex (); + HANDLE open_mutex (const char *mutex, ACCESS_MASK access); + inline HANDLE open_output_mutex (ACCESS_MASK access) + { return open_mutex (OUTPUT_MUTEX, access); } + inline HANDLE open_input_mutex (ACCESS_MASK access) + { return open_mutex (INPUT_MUTEX, access); } bool exists (); void set_master_closed () {master_pid = -1;} static void __stdcall create_master (int);