minor become/runas cleanup (#32564)

* removed/blobified unused PInvoke stuff
* added try/finally around impersonation to ensure RevertToSelf is called in all cases
* added a few explanatory comments
pull/32604/head
Matt Davis 7 years ago committed by GitHub
parent 16e98c8c5b
commit 8ecc7bc4a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -204,13 +204,8 @@ namespace Ansible
public IntPtr lpReserved; public IntPtr lpReserved;
public IntPtr lpDesktop; public IntPtr lpDesktop;
public IntPtr lpTitle; public IntPtr lpTitle;
public Int32 dwX; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 28)]
public Int32 dwY; public byte[] _data1;
public Int32 dwXSize;
public Int32 dwYSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public Int32 dwFillAttribute;
public Int32 dwFlags; public Int32 dwFlags;
public Int16 wShowWindow; public Int16 wShowWindow;
public Int16 cbReserved2; public Int16 cbReserved2;
@ -257,17 +252,6 @@ namespace Ansible
public SID_AND_ATTRIBUTES User; public SID_AND_ATTRIBUTES User;
} }
[StructLayout(LayoutKind.Sequential)]
public struct IO_COUNTERS
{
public UInt64 ReadOperationCount;
public UInt64 WriteOperationCount;
public UInt64 OtherOperationCount;
public UInt64 ReadTransferCount;
public UInt64 WriteTransferCount;
public UInt64 OtherTransferCount;
}
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
public struct JOBOBJECT_BASIC_LIMIT_INFORMATION public struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{ {
@ -283,14 +267,13 @@ namespace Ansible
} }
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
public struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION public class JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{ {
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation; public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
public IO_COUNTERS IoInfo; [MarshalAs(UnmanagedType.ByValArray, SizeConst=48)]
public UIntPtr ProcessMemoryLimit; public byte[] IO_COUNTERS_BLOB;
public UIntPtr JobMemoryLimit; [MarshalAs(UnmanagedType.ByValArray, SizeConst=4)]
public UIntPtr PeakProcessMemoryUsed; public UIntPtr[] LIMIT_BLOB;
public UIntPtr PeakJobMemoryUsed;
} }
[Flags] [Flags]
@ -305,8 +288,6 @@ namespace Ansible
CREATE_BREAKAWAY_FROM_JOB = 0x01000000, CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
CREATE_DEFAULT_ERROR_MODE = 0x04000000, CREATE_DEFAULT_ERROR_MODE = 0x04000000,
CREATE_NEW_CONSOLE = 0x00000010, CREATE_NEW_CONSOLE = 0x00000010,
CREATE_NEW_PROCESS_GROUP = 0x00000200,
CREATE_SEPARATE_WOW_VDM = 0x00000800,
CREATE_SUSPENDED = 0x00000004, CREATE_SUSPENDED = 0x00000004,
CREATE_UNICODE_ENVIRONMENT = 0x00000400, CREATE_UNICODE_ENVIRONMENT = 0x00000400,
EXTENDED_STARTUPINFO_PRESENT = 0x00080000 EXTENDED_STARTUPINFO_PRESENT = 0x00080000
@ -339,9 +320,6 @@ namespace Ansible
public enum LogonProvider public enum LogonProvider
{ {
LOGON32_PROVIDER_DEFAULT = 0, LOGON32_PROVIDER_DEFAULT = 0,
LOGON32_PROVIDER_WINNT35 = 1,
LOGON32_PROVIDER_WINNT40 = 2,
LOGON32_PROVIDER_WINNT50 = 3
} }
public enum TokenInformationClass public enum TokenInformationClass
@ -363,28 +341,12 @@ namespace Ansible
[Flags] [Flags]
public enum ProcessAccessFlags : uint public enum ProcessAccessFlags : uint
{ {
PROCESS_ALL_ACCESS = 0x001F0FFF,
PROCESS_TERMINATE = 0x00000001,
PROCESS_CREATE_THREAD = 0x00000002,
PROCESS_VM_OPERATION = 0x00000008,
PROCESS_VM_READ = 0x00000010,
PROCESS_VM_WRITE = 0x00000020,
PROCESS_DUP_HANDLE = 0x00000040,
PROCESS_CREATE_PROCESS = 0x000000080,
PROCESS_SET_QUOTA = 0x00000100,
PROCESS_SET_INFORMATION = 0x00000200,
PROCESS_QUERY_INFORMATION = 0x00000400, PROCESS_QUERY_INFORMATION = 0x00000400,
PROCESS_SUSPEND_RESUME = 0x00000800,
PROCESS_QUERY_LIMITED_INFORMATION = 0x00001000,
SYNCHRONIZE = 0x00100000
} }
public enum SECURITY_IMPERSONATION_LEVEL public enum SECURITY_IMPERSONATION_LEVEL
{ {
SecurityAnoynmous,
SecurityIdentification,
SecurityImpersonation, SecurityImpersonation,
SecurityDelegation
} }
public enum TOKEN_TYPE public enum TOKEN_TYPE
@ -395,13 +357,7 @@ namespace Ansible
enum JobObjectInfoType enum JobObjectInfoType
{ {
AssociateCompletionPortInformation = 7,
BasicLimitInformation = 2,
BasicUIRestrictions = 4,
EndOfJobTimeInformation = 6,
ExtendedLimitInformation = 9, ExtendedLimitInformation = 9,
SecurityLimitInformation = 5,
GroupInformation = 11
} }
[Flags] [Flags]
@ -455,8 +411,8 @@ namespace Ansible
private static extern bool SetInformationJobObject( private static extern bool SetInformationJobObject(
IntPtr hJob, IntPtr hJob,
JobObjectInfoType JobObjectInfoClass, JobObjectInfoType JobObjectInfoClass,
IntPtr lpJobObjectInfo, JOBOBJECT_EXTENDED_LIMIT_INFORMATION lpJobObjectInfo,
UInt32 cbJobObjectInfoLength); int cbJobObjectInfoLength);
[DllImport("kernel32.dll", SetLastError = true)] [DllImport("kernel32.dll", SetLastError = true)]
private static extern bool AssignProcessToJobObject( private static extern bool AssignProcessToJobObject(
@ -475,17 +431,11 @@ namespace Ansible
if (handle == IntPtr.Zero) if (handle == IntPtr.Zero)
throw new Win32Exception("CreateJobObject() failed"); throw new Win32Exception("CreateJobObject() failed");
JOBOBJECT_BASIC_LIMIT_INFORMATION jobInfo = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
jobInfo.LimitFlags = LimitFlags.JOB_OBJECT_LIMIT_BREAKAWAY_OK | LimitFlags.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
JOBOBJECT_EXTENDED_LIMIT_INFORMATION extendedJobInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION(); JOBOBJECT_EXTENDED_LIMIT_INFORMATION extendedJobInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
extendedJobInfo.BasicLimitInformation = jobInfo; // on OSs that support nested jobs, one of the jobs must allow breakaway for async to work properly under WinRM
extendedJobInfo.BasicLimitInformation.LimitFlags = LimitFlags.JOB_OBJECT_LIMIT_BREAKAWAY_OK | LimitFlags.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); if (!SetInformationJobObject(handle, JobObjectInfoType.ExtendedLimitInformation, extendedJobInfo, Marshal.SizeOf(extendedJobInfo)))
IntPtr pExtendedJobInfo = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(extendedJobInfo, pExtendedJobInfo, false);
if (!SetInformationJobObject(handle, JobObjectInfoType.ExtendedLimitInformation, pExtendedJobInfo, (UInt32)length))
throw new Win32Exception("SetInformationJobObject() failed"); throw new Win32Exception("SetInformationJobObject() failed");
} }
@ -628,8 +578,6 @@ namespace Ansible
{ {
SecurityIdentifier account = GetBecomeSid(username); SecurityIdentifier account = GetBecomeSid(username);
CreationFlags startup_flags = CreationFlags.CREATE_UNICODE_ENVIRONMENT | CreationFlags.CREATE_BREAKAWAY_FROM_JOB | CreationFlags.CREATE_SUSPENDED;
STARTUPINFOEX si = new STARTUPINFOEX(); STARTUPINFOEX si = new STARTUPINFOEX();
si.startupInfo.dwFlags = (int)StartupInfoFlags.USESTDHANDLES; si.startupInfo.dwFlags = (int)StartupInfoFlags.USESTDHANDLES;
@ -665,6 +613,9 @@ namespace Ansible
// Create the environment block if set // Create the environment block if set
IntPtr lpEnvironment = IntPtr.Zero; IntPtr lpEnvironment = IntPtr.Zero;
// To support async + become, we have to do some job magic later, which requires both breakaway and starting suspended
CreationFlags startup_flags = CreationFlags.CREATE_UNICODE_ENVIRONMENT | CreationFlags.CREATE_BREAKAWAY_FROM_JOB | CreationFlags.CREATE_SUSPENDED;
PROCESS_INFORMATION pi = new PROCESS_INFORMATION(); PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
// Get the user tokens to try running processes with // Get the user tokens to try running processes with
@ -761,97 +712,109 @@ namespace Ansible
GrantAccessToWindowStationAndDesktop(account); GrantAccessToWindowStationAndDesktop(account);
string account_sid = account.ToString(); string account_sid = account.ToString();
bool impersonated = false; bool impersonated = false;
IntPtr hSystemTokenDup = IntPtr.Zero;
// Try to get SYSTEM token handle so we can impersonate to get full admin token try
IntPtr hSystemToken = GetSystemUserHandle();
if (hSystemToken == IntPtr.Zero && service_sids.Contains(account_sid))
{
// We need the SYSTEM token if we want to become one of those accounts, fail here
throw new Win32Exception("Failed to get token for NT AUTHORITY\\SYSTEM");
}
else if (hSystemToken != IntPtr.Zero)
{ {
// We have the token, need to duplicate and impersonate IntPtr hSystemTokenDup = IntPtr.Zero;
bool dupResult = DuplicateTokenEx(
hSystemToken, // Try to get SYSTEM token handle so we can impersonate to get full admin token
TokenAccessLevels.MaximumAllowed, IntPtr hSystemToken = GetSystemUserHandle();
IntPtr.Zero, if (hSystemToken == IntPtr.Zero && service_sids.Contains(account_sid))
SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
TOKEN_TYPE.TokenPrimary,
out hSystemTokenDup);
int lastError = Marshal.GetLastWin32Error();
CloseHandle(hSystemToken);
if (!dupResult && service_sids.Contains(account_sid))
throw new Win32Exception(lastError, "Failed to duplicate token for NT AUTHORITY\\SYSTEM");
else if (dupResult && account_sid != "S-1-5-18")
{ {
if (ImpersonateLoggedOnUser(hSystemTokenDup)) // We need the SYSTEM token if we want to become one of those accounts, fail here
impersonated = true; throw new Win32Exception("Failed to get token for NT AUTHORITY\\SYSTEM");
else if (service_sids.Contains(account_sid))
throw new Win32Exception("Failed to impersonate as SYSTEM account");
} }
} else if (hSystemToken != IntPtr.Zero)
LogonType logonType;
string domain = null;
if (service_sids.Contains(account_sid))
{
logonType = LogonType.LOGON32_LOGON_SERVICE;
domain = "NT AUTHORITY";
password = null;
switch (account_sid)
{ {
case "S-1-5-18": // We have the token, need to duplicate and impersonate
tokens.Add(hSystemTokenDup); bool dupResult = DuplicateTokenEx(
return tokens; hSystemToken,
case "S-1-5-19": TokenAccessLevels.MaximumAllowed,
username = "LocalService"; IntPtr.Zero,
break; SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
case "S-1-5-20": TOKEN_TYPE.TokenPrimary,
username = "NetworkService"; out hSystemTokenDup);
break; int lastError = Marshal.GetLastWin32Error();
CloseHandle(hSystemToken);
if (!dupResult && service_sids.Contains(account_sid))
throw new Win32Exception(lastError, "Failed to duplicate token for NT AUTHORITY\\SYSTEM");
else if (dupResult && account_sid != "S-1-5-18")
{
if (ImpersonateLoggedOnUser(hSystemTokenDup))
impersonated = true;
else if (service_sids.Contains(account_sid))
throw new Win32Exception("Failed to impersonate as SYSTEM account");
}
// If SYSTEM impersonation failed but we're trying to become a regular user, just proceed;
// might get a limited token in UAC-enabled cases, but better than nothing...
} }
}
else LogonType logonType;
{ string domain = null;
// We are trying to become a local or domain account
logonType = LogonType.LOGON32_LOGON_INTERACTIVE; if (service_sids.Contains(account_sid))
if (username.Contains(@"\"))
{ {
var user_split = username.Split(Convert.ToChar(@"\")); // We're using a well-known service account, do a service logon instead of interactive
domain = user_split[0]; logonType = LogonType.LOGON32_LOGON_SERVICE;
username = user_split[1]; domain = "NT AUTHORITY";
password = null;
switch (account_sid)
{
case "S-1-5-18":
tokens.Add(hSystemTokenDup);
return tokens;
case "S-1-5-19":
username = "LocalService";
break;
case "S-1-5-20":
username = "NetworkService";
break;
}
} }
else if (username.Contains("@"))
domain = null;
else else
domain = "."; {
} // We are trying to become a local or domain account
logonType = LogonType.LOGON32_LOGON_INTERACTIVE;
if (username.Contains(@"\"))
{
var user_split = username.Split(Convert.ToChar(@"\"));
domain = user_split[0];
username = user_split[1];
}
else if (username.Contains("@"))
domain = null;
else
domain = ".";
}
IntPtr hToken = IntPtr.Zero; IntPtr hToken = IntPtr.Zero;
if (!LogonUser( if (!LogonUser(
username, username,
domain, domain,
password, password,
logonType, logonType,
LogonProvider.LOGON32_PROVIDER_DEFAULT, LogonProvider.LOGON32_PROVIDER_DEFAULT,
out hToken)) out hToken))
{ {
throw new Win32Exception("LogonUser failed"); throw new Win32Exception("LogonUser failed");
} }
if (!service_sids.Contains(account_sid))
{
// Try and get the elevated token for local/domain account
IntPtr hTokenElevated = GetElevatedToken(hToken);
tokens.Add(hTokenElevated);
}
if (!service_sids.Contains(account_sid)) // add the original token as a fallback
tokens.Add(hToken);
}
finally
{ {
// Try and get the elevated token for local/domain account if (impersonated)
IntPtr hTokenElevated = GetElevatedToken(hToken); RevertToSelf();
tokens.Add(hTokenElevated);
} }
tokens.Add(hToken);
if (impersonated)
RevertToSelf();
return tokens; return tokens;
} }

Loading…
Cancel
Save