diff --git a/winsup/cygwin/ChangeLog b/winsup/cygwin/ChangeLog index 31d74eed0..ddb11d82a 100644 --- a/winsup/cygwin/ChangeLog +++ b/winsup/cygwin/ChangeLog @@ -1,3 +1,33 @@ +2008-11-26 Corinna Vinschen + + * Makefile.in (DLL_OFILES): Add setlsapwd.o. + * cygserver.h (CYGWIN_SERVER_VERSION_API): Bump. + (request_code_t): Define CYGSERVER_REQUEST_SETPWD request type. + * cygserver_msg.h (client_request_msg::retval): Use default value of -1 + for retval if msglen is 0. + * cygserver_sem.h (client_request_sem::retval): Ditto. + * cygserver_shm.h (client_request_shm::retval): Ditto. + * cygserver_setpwd.h: New file. + * external.cc (cygwin_internal): Implement new CW_SET_PRIV_KEY type. + * sec_auth.cc (open_local_policy): Make externally available. + Get ACCESS_MASK as argument. + (create_token): Accommodate change to open_local_policy. + (lsaauth): Ditto. + (lsaprivkeyauth): New function fetching token by retrieving + password stored in Cygwin or Interix LSA private data area and + calling LogonUser with it. + * security.h (lsaprivkeyauth): Declare. + (open_local_policy): Declare. + * setlsapwd.cc: New file implementing setting LSA private data password + using LsaStorePrivateData or by calling cygserver if available. + * syscalls.cc (seteuid32): Add workaround to get the original token + when switching back to the original privileged user, even if + setgroups group list is still active. Add long comment to explain why. + Call lsaprivkeyauth first, only if that fails call lsaauth or + create_token. + * include/cygwin/version.h: Bump API minor number. + * include/sys/cygwin.h (cygwin_getinfo_types): Add CW_SET_PRIV_KEY. + 2008-11-21 Corinna Vinschen * fhandler_floppy.cc (fhandler_dev_floppy::raw_read): Drop diff --git a/winsup/cygwin/Makefile.in b/winsup/cygwin/Makefile.in index d6f6bb1ce..49baebc96 100644 --- a/winsup/cygwin/Makefile.in +++ b/winsup/cygwin/Makefile.in @@ -141,11 +141,11 @@ DLL_OFILES:=assert.o autoload.o bsdlib.o ctype.o cxx.o cygheap.o cygthread.o \ nftw.o ntea.o passwd.o path.o pinfo.o pipe.o poll.o posix_ipc.o \ pthread.o random.o regcomp.o regerror.o regexec.o regfree.o registry.o \ resource.o rexec.o rcmd.o scandir.o sched.o sec_acl.o sec_auth.o \ - sec_helper.o security.o select.o sem.o shared.o shm.o sigfe.o signal.o \ - sigproc.o smallprint.o spawn.o strace.o strfuncs.o strptime.o strsep.o \ - strsig.o sync.o syscalls.o sysconf.o syslog.o termios.o thread.o \ - timer.o times.o tls_pbuf.o tty.o uinfo.o uname.o wait.o wincap.o \ - window.o winf.o xsique.o \ + sec_helper.o security.o select.o sem.o setlsapwd.o shared.o shm.o \ + sigfe.o signal.o sigproc.o smallprint.o spawn.o strace.o strfuncs.o \ + strptime.o strsep.o strsig.o sync.o syscalls.o sysconf.o syslog.o \ + termios.o thread.o timer.o times.o tls_pbuf.o tty.o uinfo.o uname.o \ + wait.o wincap.o window.o winf.o xsique.o \ $(EXTRA_DLL_OFILES) $(EXTRA_OFILES) $(MALLOC_OFILES) $(MT_SAFE_OBJECTS) GMON_OFILES:=gmon.o mcount.o profil.o diff --git a/winsup/cygwin/cygserver.h b/winsup/cygwin/cygserver.h index 09ab78a9f..b549ed006 100644 --- a/winsup/cygwin/cygserver.h +++ b/winsup/cygwin/cygserver.h @@ -1,6 +1,6 @@ /* cygserver.h - Copyright 2001, 2002, 2003, 2004 Red Hat Inc. + Copyright 2001, 2002, 2003, 2004, 2008 Red Hat Inc. Written by Egor Duda @@ -20,7 +20,7 @@ details. */ #endif #define CYGWIN_SERVER_VERSION_MAJOR 1 -#define CYGWIN_SERVER_VERSION_API 3 +#define CYGWIN_SERVER_VERSION_API 4 #define CYGWIN_SERVER_VERSION_MINOR 0 #define CYGWIN_SERVER_VERSION_PATCH 0 @@ -51,6 +51,7 @@ protected: CYGSERVER_REQUEST_MSG, CYGSERVER_REQUEST_SEM, CYGSERVER_REQUEST_SHM, + CYGSERVER_REQUEST_SETPWD, CYGSERVER_REQUEST_LAST } request_code_t; diff --git a/winsup/cygwin/cygserver_msg.h b/winsup/cygwin/cygserver_msg.h index 7fbb12cab..37616bb1d 100644 --- a/winsup/cygwin/cygserver_msg.h +++ b/winsup/cygwin/cygserver_msg.h @@ -1,6 +1,6 @@ /* cygserver_msg.h: Single unix specification IPC interface for Cygwin. - Copyright 2003 Red Hat, Inc. + Copyright 2003, 2008 Red Hat, Inc. This file is part of Cygwin. @@ -75,7 +75,7 @@ public: client_request_msg (int, const void *, size_t, int); // msgsnd #endif - int retval () const { return _parameters.out.ret; } + int retval () const { return msglen () ? _parameters.out.ret : -1; } ssize_t rcvval () const { return _parameters.out.rcv; } }; diff --git a/winsup/cygwin/cygserver_sem.h b/winsup/cygwin/cygserver_sem.h index 225d8f215..fdf6f8c39 100644 --- a/winsup/cygwin/cygserver_sem.h +++ b/winsup/cygwin/cygserver_sem.h @@ -1,6 +1,6 @@ /* cygserver_sem.h: Single unix specification IPC interface for Cygwin. - Copyright 2003 Red Hat, Inc. + Copyright 2003, 2008 Red Hat, Inc. This file is part of Cygwin. @@ -71,7 +71,7 @@ public: client_request_sem (int, struct sembuf *, size_t); // semop #endif - int retval () const { return _parameters.out.ret; } + int retval () const { return msglen () ? _parameters.out.ret : -1; } }; #ifndef __INSIDE_CYGWIN__ diff --git a/winsup/cygwin/cygserver_setpwd.h b/winsup/cygwin/cygserver_setpwd.h new file mode 100644 index 000000000..94ee2998d --- /dev/null +++ b/winsup/cygwin/cygserver_setpwd.h @@ -0,0 +1,53 @@ +/* cygserver_setpwd.h: Set LSA private data password for current user. + + Copyright 2008 Red Hat, Inc. + +This file is part of Cygwin. + +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +#ifndef __CYGSERVER_SETPWD_H__ +#define __CYGSERVER_SETPWD_H__ + +#include +#include "cygserver.h" + +#define CYGWIN_LSA_KEY_PREFIX L"L$CYGWIN_" + +#ifndef __INSIDE_CYGWIN__ +class transport_layer_base; +class process_cache; +#endif + +class client_request_setpwd : public client_request +{ + friend class client_request; + +private: + union + { + struct + { + WCHAR passwd[256]; + } in; + } _parameters; + +#ifndef __INSIDE_CYGWIN__ + client_request_setpwd (); + virtual void serve (transport_layer_base *, process_cache *); +#endif + +public: + +#ifdef __INSIDE_CYGWIN__ + client_request_setpwd (PUNICODE_STRING); +#endif +}; + +#ifdef __INSIDE_CYGWIN__ +unsigned long setlsapwd (const char *passwd); +#endif + +#endif /* __CYGSERVER_SETPWD_H__ */ diff --git a/winsup/cygwin/cygserver_shm.h b/winsup/cygwin/cygserver_shm.h index 2948f6725..baa3e052a 100644 --- a/winsup/cygwin/cygserver_shm.h +++ b/winsup/cygwin/cygserver_shm.h @@ -1,6 +1,6 @@ /* cygserver_shm.h: Single unix specification IPC interface for Cygwin. - Copyright 2003 Red Hat, Inc. + Copyright 2003, 2008 Red Hat, Inc. This file is part of Cygwin. @@ -78,7 +78,7 @@ public: client_request_shm (proc *); // shmfork #endif - int retval () const { return _parameters.out.ret; } + int retval () const { return msglen () ? _parameters.out.ret : -1; } void *ptrval () const { return (void *)_parameters.out.ptr; } vm_object_t objval () const { return _parameters.out.obj; } }; diff --git a/winsup/cygwin/external.cc b/winsup/cygwin/external.cc index 675d8b066..b880e07fb 100644 --- a/winsup/cygwin/external.cc +++ b/winsup/cygwin/external.cc @@ -25,6 +25,7 @@ details. */ #include "cygtls.h" #include "child_info.h" #include "environ.h" +#include "cygserver_setpwd.h" #include #include #include @@ -357,6 +358,11 @@ cygwin_internal (cygwin_getinfo_types t, ...) dos_file_warning = va_arg (arg, int); } break; + case CW_SET_PRIV_KEY: + { + const char *passwd = va_arg (arg, const char *); + return setlsapwd (passwd); + } default: break; diff --git a/winsup/cygwin/include/cygwin/version.h b/winsup/cygwin/include/cygwin/version.h index 268f99db7..ef332ce23 100644 --- a/winsup/cygwin/include/cygwin/version.h +++ b/winsup/cygwin/include/cygwin/version.h @@ -335,12 +335,13 @@ details. */ 186: Remove ancient V8 regexp functions. Also eliminate old crt0 interface which provided its own user_data structure. 187: Export cfmakeraw. + 188: Export CW_SET_PRIV_KEY. */ /* Note that we forgot to bump the api for ualarm, strtoll, strtoull */ #define CYGWIN_VERSION_API_MAJOR 0 -#define CYGWIN_VERSION_API_MINOR 187 +#define CYGWIN_VERSION_API_MINOR 188 /* There is also a compatibity version number associated with the shared memory regions. It is incremented when incompatible diff --git a/winsup/cygwin/include/sys/cygwin.h b/winsup/cygwin/include/sys/cygwin.h index 1ba18597d..8c1101051 100644 --- a/winsup/cygwin/include/sys/cygwin.h +++ b/winsup/cygwin/include/sys/cygwin.h @@ -140,7 +140,8 @@ typedef enum CW_DEBUG_SELF, CW_SYNC_WINENV, CW_CYGTLS_PADSIZE, - CW_SET_DOS_FILE_WARNING + CW_SET_DOS_FILE_WARNING, + CW_SET_PRIV_KEY } cygwin_getinfo_types; #define CW_NEXTPID 0x80000000 /* or with pid to get next one */ diff --git a/winsup/cygwin/sec_auth.cc b/winsup/cygwin/sec_auth.cc index 3f7a082b2..138131744 100644 --- a/winsup/cygwin/sec_auth.cc +++ b/winsup/cygwin/sec_auth.cc @@ -27,6 +27,7 @@ details. */ #include #include "pwdgrp.h" #include "cyglsa.h" +#include "cygserver_setpwd.h" #include extern "C" void @@ -150,13 +151,13 @@ str2uni_cat (UNICODE_STRING &tgt, const char *srcstr) tgt.Length = tgt.MaximumLength = 0; } -static LSA_HANDLE -open_local_policy () +HANDLE +open_local_policy (ACCESS_MASK access) { LSA_OBJECT_ATTRIBUTES oa = { 0, 0, 0, 0, 0, 0 }; - LSA_HANDLE lsa = INVALID_HANDLE_VALUE; + HANDLE lsa = INVALID_HANDLE_VALUE; - NTSTATUS ret = LsaOpenPolicy (NULL, &oa, POLICY_EXECUTE, &lsa); + NTSTATUS ret = LsaOpenPolicy (NULL, &oa, access, &lsa); if (ret != STATUS_SUCCESS) __seterrno_from_win_error (LsaNtStatusToWinError (ret)); return lsa; @@ -785,7 +786,7 @@ create_token (cygsid &usersid, user_groups &new_groups, struct passwd *pw) push_self_privilege (SE_CREATE_TOKEN_PRIVILEGE, true); /* Open policy object. */ - if ((lsa = open_local_policy ()) == INVALID_HANDLE_VALUE) + if ((lsa = open_local_policy (POLICY_EXECUTE)) == INVALID_HANDLE_VALUE) goto out; /* User, owner, primary group. */ @@ -963,7 +964,7 @@ lsaauth (cygsid &usersid, user_groups &new_groups, struct passwd *pw) } /* Open policy object. */ - if ((lsa = open_local_policy ()) == INVALID_HANDLE_VALUE) + if ((lsa = open_local_policy (POLICY_EXECUTE)) == INVALID_HANDLE_VALUE) goto out; /* Create origin. */ @@ -1172,3 +1173,75 @@ out: debug_printf ("%p = lsaauth ()", user_token); return user_token; } + +#define SFU_LSA_KEY_SUFFIX L"_microsoft_sfu_utility" + +HANDLE +lsaprivkeyauth (struct passwd *pw) +{ + NTSTATUS status; + HANDLE lsa = INVALID_HANDLE_VALUE; + HANDLE token = NULL; + WCHAR sid[256]; + WCHAR domain[MAX_DOMAIN_NAME_LEN + 1]; + WCHAR user[UNLEN + 1]; + WCHAR key_name[MAX_DOMAIN_NAME_LEN + UNLEN + wcslen (SFU_LSA_KEY_SUFFIX) + 2]; + UNICODE_STRING key; + PUNICODE_STRING data; + cygsid psid; + + push_self_privilege (SE_TCB_PRIVILEGE, true); + + /* Open policy object. */ + if ((lsa = open_local_policy (POLICY_GET_PRIVATE_INFORMATION)) + == INVALID_HANDLE_VALUE) + goto out; + + /* Needed for Interix key and LogonUser. */ + extract_nt_dom_user (pw, domain, user); + + /* First test for a Cygwin entry. */ + if (psid.getfrompw (pw) && psid.string (sid)) + { + wcpcpy (wcpcpy (key_name, CYGWIN_LSA_KEY_PREFIX), sid); + RtlInitUnicodeString (&key, key_name); + status = LsaRetrievePrivateData (lsa, &key, &data); + if (!NT_SUCCESS (status)) + { + /* No Cygwin key, try Interix key. */ + if (!*domain) + goto out; + __small_swprintf (key_name, L"%W_%W%W", + domain, user, SFU_LSA_KEY_SUFFIX); + RtlInitUnicodeString (&key, key_name); + status = LsaRetrievePrivateData (lsa, &key, &data); + if (!NT_SUCCESS (status)) + goto out; + } + } + + /* The key is not 0-terminated. */ + PWCHAR passwd = (PWCHAR) alloca (data->Length + sizeof (WCHAR)); + *wcpncpy (passwd, data->Buffer, data->Length / sizeof (WCHAR)) = L'\0'; + LsaFreeMemory (data); + debug_printf ("Try logon for %W\\%W", domain, user); + if (!LogonUserW (user, domain, passwd, LOGON32_LOGON_INTERACTIVE, + LOGON32_PROVIDER_DEFAULT, &token)) + { + __seterrno (); + token = NULL; + } + else if (!SetHandleInformation (token, + HANDLE_FLAG_INHERIT, + HANDLE_FLAG_INHERIT)) + { + __seterrno (); + CloseHandle (token); + token = NULL; + } + +out: + close_local_policy (lsa); + pop_self_privilege (); + return token; +} diff --git a/winsup/cygwin/security.h b/winsup/cygwin/security.h index 27a2d0046..0f194bade 100644 --- a/winsup/cygwin/security.h +++ b/winsup/cygwin/security.h @@ -370,6 +370,8 @@ void __stdcall str2uni_cat (_UNICODE_STRING &, const char *) __attribute__ ((reg HANDLE create_token (cygsid &usersid, user_groups &groups, struct passwd * pw); /* LSA authentication function. */ HANDLE lsaauth (cygsid &, user_groups &, struct passwd *); +/* LSA private key storage authentication, same as when using service logons. */ +HANDLE lsaprivkeyauth (struct passwd *pw); /* Verify an existing token */ bool verify_token (HANDLE token, cygsid &usersid, user_groups &groups, bool *pintern = NULL); /* Get groups of a user */ @@ -380,6 +382,8 @@ void extract_nt_dom_user (const struct passwd *pw, PWCHAR domain, PWCHAR user); /* Get default logonserver for a domain. */ bool get_logon_server (PWCHAR domain, PWCHAR wserver, bool rediscovery); +HANDLE open_local_policy (ACCESS_MASK access); + /* sec_helper.cc: Security helper functions. */ int set_privilege (HANDLE token, DWORD privilege, bool enable); void set_cygwin_privileges (HANDLE token); diff --git a/winsup/cygwin/setlsapwd.cc b/winsup/cygwin/setlsapwd.cc new file mode 100644 index 000000000..34284afd3 --- /dev/null +++ b/winsup/cygwin/setlsapwd.cc @@ -0,0 +1,90 @@ +/* setlsapwd.cc: Set LSA private data password for current user. + + Copyright 2008 Red Hat, Inc. + +This file is part of Cygwin. + +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +#include "winsup.h" +#include "shared_info.h" +#include "cygerrno.h" +#include "path.h" +#include "fhandler.h" +#include "dtable.h" +#include "cygheap.h" +#include "security.h" +#include "cygserver_setpwd.h" +#include "ntdll.h" +#include +#include +#include + +#ifdef USE_SERVER +/* + * client_request_setpwd Constructor + */ + +client_request_setpwd::client_request_setpwd (PUNICODE_STRING passwd) + : client_request (CYGSERVER_REQUEST_SETPWD, &_parameters, sizeof (_parameters)) +{ + memset (_parameters.in.passwd, 0, sizeof _parameters.in.passwd); + if (passwd->Length > 0 && passwd->Length < 256 * sizeof (WCHAR)) + wcpncpy (_parameters.in.passwd, passwd->Buffer, 255); + + msglen (sizeof (_parameters.in)); +} + +#endif /* USE_SERVER */ + +unsigned long +setlsapwd (const char *passwd) +{ + unsigned long ret = (unsigned long) -1; + HANDLE lsa = INVALID_HANDLE_VALUE; + WCHAR sid[128]; + WCHAR key_name[128 + wcslen (CYGWIN_LSA_KEY_PREFIX)]; + PWCHAR data_buf = NULL; + UNICODE_STRING key; + UNICODE_STRING data; + + wcpcpy (wcpcpy (key_name, CYGWIN_LSA_KEY_PREFIX), + cygheap->user.get_windows_id (sid)); + RtlInitUnicodeString (&key, key_name); + if (!passwd || ! *passwd + || sys_mbstowcs_alloc (&data_buf, HEAP_NOTHEAP, passwd)) + { + NTSTATUS status = STATUS_ACCESS_DENIED; + + memset (&data, 0, sizeof data); + if (data_buf) + RtlInitUnicodeString (&data, data_buf); + /* First try it locally. Works for admin accounts. */ + if ((lsa = open_local_policy (POLICY_CREATE_SECRET)) + != INVALID_HANDLE_VALUE) + { + status = LsaStorePrivateData (lsa, &key, data.Length ? &data : NULL); + if (NT_SUCCESS (status)) + ret = 0; + LsaClose (lsa); + } + if (ret) +#ifdef USE_SERVER + { + /* If that fails, ask cygserver. */ + client_request_setpwd request (&data); + if (request.make_request () == -1 || request.error_code ()) + set_errno (request.error_code ()); + else + ret = 0; + } +#else + __seterrno_from_nt_status (status); +#endif + if (data_buf) + free (data_buf); + } + return ret; +} diff --git a/winsup/cygwin/syscalls.cc b/winsup/cygwin/syscalls.cc index 9b39f7712..4590c37a8 100644 --- a/winsup/cygwin/syscalls.cc +++ b/winsup/cygwin/syscalls.cc @@ -2493,7 +2493,23 @@ seteuid32 (__uid32_t uid) cygheap->user.deimpersonate (); /* Verify if the process token is suitable. */ - if (verify_token (hProcToken, usersid, groups)) + /* TODO, CV 2008-11-25: The check against saved_sid is a kludge and a + shortcut. We must check if it's really feasible in the long run. + The reason to add this shortcut is this: sshd switches back to the + privileged user running sshd at least twice in the process of + authentication. It calls seteuid first, then setegid. Due to this + order, the setgroups group list is still active when calling seteuid + and verify_token treats the original token of the privileged user as + insufficient. This in turn results in creating a new user token for + the privileged user instead of using the orignal token. This can have + unfortunate side effects. The created token has different group + memberships, different user rights, and misses possible network + credentials. + Therefore we try this shortcut now. When switching back to the + privileged user, we probably always want a correct (aka original) + user token for this privileged user, not only in sshd. */ + if ((uid == cygheap->user.saved_uid && usersid == cygheap->user.saved_sid ()) + || verify_token (hProcToken, usersid, groups)) new_token = hProcToken; /* Verify if the external token is suitable */ else if (cygheap->user.external_token != NO_IMPERSONATION @@ -2514,19 +2530,35 @@ seteuid32 (__uid32_t uid) debug_printf ("Found token %d", new_token); - /* If no impersonation token is available, try to - authenticate using NtCreateToken () or LSA authentication. */ + /* If no impersonation token is available, try to authenticate using + LSA private data stored password, LSA authentication using our own + LSA module, or, as last chance, NtCreateToken. */ if (new_token == INVALID_HANDLE_VALUE) { - if (!(new_token = lsaauth (usersid, groups, pw_new))) - { - debug_printf ("lsaauth failed, try create_token."); - new_token = create_token (usersid, groups, pw_new); - if (new_token == INVALID_HANDLE_VALUE) + new_token = lsaprivkeyauth (pw_new); + if (new_token) + { + /* We have to verify this token since settings in /etc/group + might render it unusable im terms of group membership. */ + if (!verify_token (new_token, usersid, groups)) { - debug_printf ("create_token failed, bail out of here"); - cygheap->user.reimpersonate (); - return -1; + CloseHandle (new_token); + new_token = NULL; + } + } + if (!new_token) + { + debug_printf ("lsaprivkeyauth failed, try lsaauth."); + if (!(new_token = lsaauth (usersid, groups, pw_new))) + { + debug_printf ("lsaauth failed, try create_token."); + new_token = create_token (usersid, groups, pw_new); + if (new_token == INVALID_HANDLE_VALUE) + { + debug_printf ("create_token failed, bail out of here"); + cygheap->user.reimpersonate (); + return -1; + } } }