From 0fb497165f8545470624012315aeaf37333c1ea2 Mon Sep 17 00:00:00 2001 From: Corinna Vinschen Date: Sat, 26 Jan 2019 18:33:41 +0100 Subject: [PATCH] Cygwin: seteuid: use Kerberos/MsV1_0 S4U authentication by default - This simple and official method replaces cyglsa and "create token" methods. No network share access, same as before. - lsaauth and create_token are disabled now. If problems crop up, they can be easily reactivated. If no problems crop up, they can be removed in a while, together with the lsaauth subdir. - Bump Cygwin version to 3.0. Signed-off-by: Corinna Vinschen --- winsup/cygwin/autoload.cc | 1 + winsup/cygwin/include/cygwin/version.h | 2 +- winsup/cygwin/release/{2.12.0 => 3.0} | 3 + winsup/cygwin/sec_auth.cc | 265 ++++++++++++++++++++++++- winsup/cygwin/security.h | 2 + winsup/cygwin/syscalls.cc | 26 ++- winsup/doc/new-features.xml | 13 +- winsup/doc/ntsec.xml | 120 +++++------ 8 files changed, 353 insertions(+), 79 deletions(-) rename winsup/cygwin/release/{2.12.0 => 3.0} (95%) diff --git a/winsup/cygwin/autoload.cc b/winsup/cygwin/autoload.cc index b4dc77339..73367dfd8 100644 --- a/winsup/cygwin/autoload.cc +++ b/winsup/cygwin/autoload.cc @@ -647,6 +647,7 @@ LoadDLLfunc (LsaFreeReturnBuffer, 4, secur32) LoadDLLfunc (LsaLogonUser, 56, secur32) LoadDLLfunc (LsaLookupAuthenticationPackage, 12, secur32) LoadDLLfunc (LsaRegisterLogonProcess, 12, secur32) +LoadDLLfunc (TranslateNameW, 20, secur32) LoadDLLfunc (SHGetDesktopFolder, 4, shell32) diff --git a/winsup/cygwin/include/cygwin/version.h b/winsup/cygwin/include/cygwin/version.h index 478d080f5..b5ba93f5a 100644 --- a/winsup/cygwin/include/cygwin/version.h +++ b/winsup/cygwin/include/cygwin/version.h @@ -10,7 +10,7 @@ details. */ the Cygwin shared library". This version is used to track important changes to the DLL and is mainly informative in nature. */ -#define CYGWIN_VERSION_DLL_MAJOR 2012 +#define CYGWIN_VERSION_DLL_MAJOR 3000 #define CYGWIN_VERSION_DLL_MINOR 0 /* Major numbers before CYGWIN_VERSION_DLL_EPOCH are incompatible. */ diff --git a/winsup/cygwin/release/2.12.0 b/winsup/cygwin/release/3.0 similarity index 95% rename from winsup/cygwin/release/2.12.0 rename to winsup/cygwin/release/3.0 index 19e05565a..79affdb27 100644 --- a/winsup/cygwin/release/2.12.0 +++ b/winsup/cygwin/release/3.0 @@ -58,6 +58,9 @@ What changed: - Improve uname(2) for newly built applications. +- Kerberos/MSV1_0 S4U authentication replaces two old methods: + Creating a token from scratch and Cygwin LSA authentication package. + Bug Fixes --------- diff --git a/winsup/cygwin/sec_auth.cc b/winsup/cygwin/sec_auth.cc index 54053dfb5..21cb0727f 100644 --- a/winsup/cygwin/sec_auth.cc +++ b/winsup/cygwin/sec_auth.cc @@ -24,6 +24,8 @@ details. */ #include #include #include +#define SECURITY_WIN32 +#include #include "cyglsa.h" #include "cygserver_setpwd.h" #include @@ -872,6 +874,32 @@ verify_token (HANDLE token, cygsid &usersid, user_groups &groups, bool *pintern) || groups.pgsid == usersid; } +const char * +account_restriction (NTSTATUS status) +{ + const char *type; + + switch (status) + { + case STATUS_INVALID_LOGON_HOURS: + type = "Logon outside allowed hours"; + break; + case STATUS_INVALID_WORKSTATION: + type = "Logon at this machine not allowed"; + break; + case STATUS_PASSWORD_EXPIRED: + type = "Password expired"; + break; + case STATUS_ACCOUNT_DISABLED: + type = "Account disabled"; + break; + default: + type = "Unknown"; + break; + } + return type; +} + HANDLE create_token (cygsid &usersid, user_groups &new_groups) { @@ -1229,7 +1257,11 @@ lsaauth (cygsid &usersid, user_groups &new_groups) &sub_status); if (status != STATUS_SUCCESS) { - debug_printf ("LsaLogonUser: %y (sub-status %y)", status, sub_status); + if (status == STATUS_ACCOUNT_RESTRICTION) + debug_printf ("Cygwin LSA Auth LsaLogonUser failed: %y (%s)", + status, account_restriction (sub_status)); + else + debug_printf ("Cygwin LSA Auth LsaLogonUser failed: %y", status); __seterrno_from_nt_status (status); goto out; } @@ -1338,3 +1370,234 @@ out: pop_self_privilege (); return token; } + +/* The following code is inspired by the generate_s4u_user_token + and lookup_principal_name functions from + https://github.com/PowerShell/openssh-portable + + Thanks guys! For courtesy here's the original copyright disclaimer: */ + +/* +* Author: Manoj Ampalam +* Utilities to generate user tokens +* +* Author: Bryan Berns +* Updated s4u, logon, and profile loading routines to use +* normalized login names. +* +* Copyright (c) 2015 Microsoft Corp. +* All rights reserved +* +* Microsoft openssh win32 port +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* In Mingw-w64, MsV1_0S4ULogon and MSV1_0_S4U_LOGON are only defined + in ddk/ntifs.h. We can't inlcude this. */ + +#define MsV1_0S4ULogon ((MSV1_0_LOGON_SUBMIT_TYPE) 12) + +typedef struct _MSV1_0_S4U_LOGON +{ + MSV1_0_LOGON_SUBMIT_TYPE MessageType; + ULONG Flags; + UNICODE_STRING UserPrincipalName; + UNICODE_STRING DomainName; +} MSV1_0_S4U_LOGON, *PMSV1_0_S4U_LOGON; + +HANDLE +s4uauth (struct passwd *pw) +{ + LSA_STRING name; + HANDLE lsa_hdl = NULL; + LSA_OPERATIONAL_MODE sec_mode; + NTSTATUS status, sub_status; + WCHAR domain[MAX_DOMAIN_NAME_LEN + 1]; + WCHAR user[UNLEN + 1]; + bool try_kerb_auth; + ULONG package_id, size; + struct { + LSA_STRING str; + CHAR buf[16]; + } origin; + + tmp_pathbuf tp; + PVOID authinf = NULL; + ULONG authinf_size; + TOKEN_SOURCE ts; + PKERB_INTERACTIVE_PROFILE profile = NULL; + LUID luid; + QUOTA_LIMITS quota; + HANDLE token = NULL; + + push_self_privilege (SE_TCB_PRIVILEGE, true); + + /* Register as logon process. */ + RtlInitAnsiString (&name, "Cygwin"); + status = LsaRegisterLogonProcess (&name, &lsa_hdl, &sec_mode); + if (status != STATUS_SUCCESS) + { + debug_printf ("LsaRegisterLogonProcess: %y", status); + __seterrno_from_nt_status (status); + goto out; + } + + /* Fetch user and domain name and check if this is a domain user. + If so, try Kerberos first. */ + extract_nt_dom_user (pw, domain, user); + try_kerb_auth = cygheap->dom.member_machine () + && wcscasecmp (domain, cygheap->dom.account_flat_name ()); + RtlInitAnsiString (&name, try_kerb_auth ? MICROSOFT_KERBEROS_NAME_A + : MSV1_0_PACKAGE_NAME); + status = LsaLookupAuthenticationPackage (lsa_hdl, &name, &package_id); + if (status != STATUS_SUCCESS) + { + debug_printf ("LsaLookupAuthenticationPackage: %y", status); + __seterrno_from_nt_status (status); + goto out; + } + /* Create origin. */ + stpcpy (origin.buf, "Cygwin"); + RtlInitAnsiString (&origin.str, origin.buf); + + if (try_kerb_auth) + { + PWCHAR sam_name = tp.w_get (); + PWCHAR upn_name = tp.w_get (); + size = NT_MAX_PATH; + KERB_S4U_LOGON *s4u_logon; + USHORT name_len; + + wcpcpy (wcpcpy (wcpcpy (sam_name, domain), L"\\"), user); + if (TranslateNameW (sam_name, NameSamCompatible, NameUserPrincipal, + upn_name, &size) == 0) + { + PWCHAR translated_name = tp.w_get (); + PWCHAR p; + + debug_printf ("TranslateNameW(%W, NameUserPrincipal) %E", sam_name); + size = NT_MAX_PATH; + if (TranslateNameW (sam_name, NameSamCompatible, NameCanonical, + translated_name, &size) == 0) + { + debug_printf ("TranslateNameW(%W, NameCanonical) %E", sam_name); + debug_printf ("Fallback to MsV1_0 auth"); + goto msv1_0_auth; /* Fall through to MSV1_0 authentication */ + } + p = wcschr (translated_name, L'/'); + if (p) + *p = '\0'; + wcpcpy (wcpcpy (wcpcpy (upn_name, user), L"@"), translated_name); + } + + name_len = wcslen (upn_name) * sizeof (WCHAR); + authinf_size = sizeof (KERB_S4U_LOGON) + name_len; + authinf = tp.c_get (); + RtlSecureZeroMemory (authinf, authinf_size); + s4u_logon = (KERB_S4U_LOGON *) authinf; + s4u_logon->MessageType = KerbS4ULogon; + s4u_logon->Flags = 0; + /* Append user to login info */ + RtlInitEmptyUnicodeString (&s4u_logon->ClientUpn, + (PWCHAR) (s4u_logon + 1), + name_len); + RtlAppendUnicodeToString (&s4u_logon->ClientUpn, upn_name); + debug_printf ("ClientUpn: <%S>", &s4u_logon->ClientUpn); + /* Create token source. */ + memcpy (ts.SourceName, "Cygwin.1", 8); + ts.SourceIdentifier.HighPart = 0; + ts.SourceIdentifier.LowPart = 0x0105; + status = LsaLogonUser (lsa_hdl, (PLSA_STRING) &origin, Network, + package_id, authinf, authinf_size, NULL, + &ts, (PVOID *) &profile, &size, + &luid, &token, "a, &sub_status); + switch (status) + { + case STATUS_SUCCESS: + goto out; + /* These failures are fatal */ + case STATUS_QUOTA_EXCEEDED: + case STATUS_LOGON_FAILURE: + debug_printf ("Kerberos S4U LsaLogonUser failed: %y", status); + goto out; + case STATUS_ACCOUNT_RESTRICTION: + debug_printf ("Kerberos S4U LsaLogonUser failed: %y (%s)", + status, account_restriction (sub_status)); + goto out; + default: + break; + } + debug_printf ("Kerberos S4U LsaLogonUser failed: %y, try MsV1_0", status); + /* Fall through to MSV1_0 authentication */ + } + +msv1_0_auth: + MSV1_0_S4U_LOGON *s4u_logon; + USHORT user_len, domain_len; + + user_len = wcslen (user) * sizeof (WCHAR); + domain_len = wcslen (domain) * sizeof (WCHAR); /* Local machine */ + authinf_size = sizeof (MSV1_0_S4U_LOGON) + user_len + domain_len; + if (!authinf) + authinf = tp.c_get (); + RtlSecureZeroMemory (authinf, authinf_size); + s4u_logon = (MSV1_0_S4U_LOGON *) authinf; + s4u_logon->MessageType = MsV1_0S4ULogon; + s4u_logon->Flags = 0; + /* Append user and domain to login info */ + RtlInitEmptyUnicodeString (&s4u_logon->UserPrincipalName, + (PWCHAR) (s4u_logon + 1), + user_len); + RtlInitEmptyUnicodeString (&s4u_logon->DomainName, + (PWCHAR) ((PBYTE) (s4u_logon + 1) + user_len), + domain_len); + RtlAppendUnicodeToString (&s4u_logon->UserPrincipalName, user); + RtlAppendUnicodeToString (&s4u_logon->DomainName, domain); + debug_printf ("DomainName: <%S> UserPrincipalName: <%S>", + &s4u_logon->DomainName, &s4u_logon->UserPrincipalName); + /* Create token source. */ + memcpy (ts.SourceName, "Cygwin.1", 8); + ts.SourceIdentifier.HighPart = 0; + ts.SourceIdentifier.LowPart = 0x0106; + if ((status = LsaLogonUser (lsa_hdl, (PLSA_STRING) &origin, Network, + package_id, authinf, authinf_size, NULL, + &ts, (PVOID *) &profile, &size, + &luid, &token, "a, &sub_status)) + != STATUS_SUCCESS) + { + if (status == STATUS_ACCOUNT_RESTRICTION) + debug_printf ("MSV1_0 S4U LsaLogonUser failed: %y (%s)", + status, account_restriction (sub_status)); + else + debug_printf ("MSV1_0 S4U LsaLogonUser failed: %y", status); + } + +out: + if (lsa_hdl) + LsaDeregisterLogonProcess (lsa_hdl); + if (profile) + LsaFreeReturnBuffer (profile); + + pop_self_privilege (); + return token; +} diff --git a/winsup/cygwin/security.h b/winsup/cygwin/security.h index 5104bb7d2..70912b4fc 100644 --- a/winsup/cygwin/security.h +++ b/winsup/cygwin/security.h @@ -479,6 +479,8 @@ HANDLE create_token (cygsid &usersid, user_groups &groups); HANDLE lsaauth (cygsid &, user_groups &); /* LSA private key storage authentication, same as when using service logons. */ HANDLE lsaprivkeyauth (struct passwd *pw); +/* Kerberos or MsV1 S4U logon. */ +HANDLE s4uauth (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 */ diff --git a/winsup/cygwin/syscalls.cc b/winsup/cygwin/syscalls.cc index 978bd424e..8a995e8fb 100644 --- a/winsup/cygwin/syscalls.cc +++ b/winsup/cygwin/syscalls.cc @@ -3494,7 +3494,7 @@ seteuid32 (uid_t uid) 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 + the privileged user instead of using the original token. This can have unfortunate side effects. The created token has different group memberships, different user rights, and misses possible network credentials. @@ -3542,17 +3542,31 @@ seteuid32 (uid_t uid) } if (!new_token) { +#if 1 + debug_printf ("lsaprivkeyauth failed, try s4uauth."); + if (!(new_token = s4uauth (pw_new))) + { + debug_printf ("s4uauth failed, bail out"); + cygheap->user.reimpersonate (); + return -1; + } +#else debug_printf ("lsaprivkeyauth failed, try lsaauth."); if (!(new_token = lsaauth (usersid, groups))) { - debug_printf ("lsaauth failed, try create_token."); - if (!(new_token = create_token (usersid, groups))) + debug_printf ("lsaauth failed, try s4uauth."); + if (!(new_token = s4uauth (pw_new))) { - debug_printf ("create_token failed, bail out"); - cygheap->user.reimpersonate (); - return -1; + debug_printf ("s4uauth failed, try create_token."); + if (!(new_token = create_token (usersid, groups))) + { + debug_printf ("create_token failed, bail out"); + cygheap->user.reimpersonate (); + return -1; + } } } +#endif } /* Keep at most one internal token */ diff --git a/winsup/doc/new-features.xml b/winsup/doc/new-features.xml index ae5088d27..d620d1c2c 100644 --- a/winsup/doc/new-features.xml +++ b/winsup/doc/new-features.xml @@ -4,7 +4,7 @@ What's new and what changed in Cygwin -What's new and what changed in 2.12 +What's new and what changed in 3.0 @@ -86,15 +86,20 @@ to free the parent directory. Wctype functions updated to Unicode 11.0. - + Remove matherr, and SVID and X/Open math library configurations. Default math library configuration is now IEEE. - - + + Improve uname(2) for newly built applications. + +Kerberos/MSV1_0 S4U authentication replaces two old methods: +Creating a token from scratch and Cygwin LSA authentication package. + + diff --git a/winsup/doc/ntsec.xml b/winsup/doc/ntsec.xml index 03293591b..e8419c5bc 100644 --- a/winsup/doc/ntsec.xml +++ b/winsup/doc/ntsec.xml @@ -2326,35 +2326,56 @@ example: -Switching the user context without password, Method 1: Create a token from scratch +Switching the user context without password, Method 1: Kerberos/MsV1_0 S4U authentication An unfortunate aspect of the implementation of set(e)uid is the fact that the calling process -requires the password of the user to which to switch. Applications such as +requires the password of the user to switch to. Applications such as sshd wishing to switch the user context after a successful public key authentication, or the cron application which, again, wants to switch the user without any authentication are stuck here. But there are other ways to get new user tokens. -One way is just to create a user token from scratch. This is -accomplished by using an (officially undocumented) function on the NT -function level. The NT function level is used to implement the Win32 -level, and, as such is closer to the kernel than the Win32 level. The -function of interest, NtCreateToken, allows you to -specify user, groups, permissions and almost everything you need to -create a user token, without the need to specify the user password. The -only restriction for using this function is that the calling process -needs the "Create a token object" user right, which only the SYSTEM user -account has by default, and which is considered the most dangerous right -a user can have on Windows systems. - -That sounds good. We just start the servers which have to switch -the user context (sshd, inetd, -cron, ...) as Windows services under the SYSTEM -(or LocalSystem in the GUI) account and everything just works. -Unfortunately that's too simple. Using NtCreateToken -has a few drawbacks. +Starting with Cygwin 3.0, Cygwin tries to create a token by using +Windows S4U authentication by default. For a quick +description, see +this blog posting. Cygwin versions prior +to 3.0 tried to creat a user token from scratch using an officially +undocumented function NtCreateToken which +is now disabled. + +So we just start the servers which have to switch the user context +(sshd, inetd, cron, +...) as Windows services under the SYSTEM (or LocalSystem in the GUI) +account and everything just works. Unfortunately that's too simple. +Using S4U has a drawback. + +Annoyingly, you don't have the usual comfortable access +to network shares. The reason is that the token has been created +without knowing the password. The password are your credentials +necessary for network access. Thus, if you logon with a password, the +password is stored hidden as "token credentials" within the access token +and used as default logon to access network resources. Since these +credentials are missing from the token created with S4U +or NtCreateToken, you only can access network shares +from the new user's process tree by using explicit authentication, on +the command line for instance: + +bash$ net use '\\server\share' /user:DOMAIN\my_user my_users_password + + +Note that, on some systems, you can't even define a drive letter +to access the share, and under some circumstances the drive letter you +choose collides with a drive letter already used in another session. +Therefore it's better to get used to accessing these shares using the UNC +path as in + + +bash$ grep foo //server/share/foofile + + + -Switching the user context without password, Method 3: With password +Switching the user context without password, Method 2: With password -Ok, so we have solved almost any problem, except for the network -access problem. Not being able to access network shares without -having to specify a cleartext password on the command line or in a -script is a harsh problem for automated logons for testing purposes -and similar stuff. +Not being able to access network shares without having to specify +a cleartext password on the command line or in a script is a harsh problem +for automated logons for testing purposes and similar stuff. Fortunately there is a solution, but it has its own drawbacks. But, first things first, how does it work? The title of this section @@ -2512,13 +2504,7 @@ as normal, non-privileged user as well. hidden, obfuscated registry area. Only SYSTEM has access to this area for listing purposes, so, even as an administrator, you can't examine this area with regedit. Right? No. Every administrator can start -regedit as SYSTEM user: - - -bash$ date -Tue Dec 2 16:28:03 CET 2008 -bash$ at 16:29 /interactive regedit.exe - +regedit as SYSTEM user, the Internet is your friend here. Additionally, if an administrator knows under which name the private key is stored (which is well-known since the algorithms @@ -2539,12 +2525,12 @@ safely use this method. Switching the user context, how does it all fit together? -Now we learned about four different ways to switch the user +Now we learned about three different ways to switch the user context using the set(e)uid system call, but how does set(e)uid really work? Which method does it use now? -The answer is, all four of them. So here's a brief overview +The answer is, all three of them. So here's a brief overview what set(e)uid does under the hood: @@ -2575,17 +2561,17 @@ private registry area, either under a Cygwin key, or under a SFU key. If so, use this to call LogonUser. If this succeeds, we use the resulting token for the user context switch. - + -Last chance, try to use the NtCreateToken call -to create a token. If that works, use this token. +Otherwise, use the default S4U authentication +to create a token. -- 2.43.5