Skip to content

Commit

Permalink
On macOS the gss_accept_sec_context/gss_init_sec_context APIs release…
Browse files Browse the repository at this point in the history
… the context handle

when error occurs. The code didn't handle it properly and it would result in double-free
and hard crash. Update the code to handle this situation properly.
  • Loading branch information
filipnavara committed Jun 30, 2022
1 parent 14dac22 commit 00b2786
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 195 deletions.
306 changes: 119 additions & 187 deletions src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,146 +85,6 @@ private static int GssUnwrap(
}
}

private static bool GssInitSecurityContext(
ref SafeGssContextHandle? context,
SafeGssCredHandle credential,
bool isNtlm,
ChannelBinding? channelBinding,
SafeGssNameHandle? targetName,
Interop.NetSecurityNative.GssFlags inFlags,
ReadOnlySpan<byte> buffer,
out byte[]? outputBuffer,
out uint outFlags,
out bool isNtlmUsed)
{
outputBuffer = null;
outFlags = 0;

// EstablishSecurityContext is called multiple times in a session.
// In each call, we need to pass the context handle from the previous call.
// For the first call, the context handle will be null.
bool newContext = false;
if (context == null)
{
newContext = true;
context = new SafeGssContextHandle();
}

Interop.NetSecurityNative.GssBuffer token = default(Interop.NetSecurityNative.GssBuffer);
Interop.NetSecurityNative.Status status;

try
{
Interop.NetSecurityNative.Status minorStatus;

if (channelBinding != null)
{
// If a TLS channel binding token (cbt) is available then get the pointer
// to the application specific data.
int appDataOffset = Marshal.SizeOf<SecChannelBindings>();
Debug.Assert(appDataOffset < channelBinding.Size);
IntPtr cbtAppData = channelBinding.DangerousGetHandle() + appDataOffset;
int cbtAppDataSize = channelBinding.Size - appDataOffset;
status = Interop.NetSecurityNative.InitSecContext(out minorStatus,
credential,
ref context,
isNtlm,
cbtAppData,
cbtAppDataSize,
targetName,
(uint)inFlags,
buffer,
ref token,
out outFlags,
out isNtlmUsed);
}
else
{
status = Interop.NetSecurityNative.InitSecContext(out minorStatus,
credential,
ref context,
isNtlm,
targetName,
(uint)inFlags,
buffer,
ref token,
out outFlags,
out isNtlmUsed);
}

if ((status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE) &&
(status != Interop.NetSecurityNative.Status.GSS_S_CONTINUE_NEEDED))
{
if (newContext)
{
context.Dispose();
context = null;
}
throw new Interop.NetSecurityNative.GssApiException(status, minorStatus);
}

outputBuffer = token.ToByteArray();
}
finally
{
token.Dispose();
}

return status == Interop.NetSecurityNative.Status.GSS_S_COMPLETE;
}

private static bool GssAcceptSecurityContext(
ref SafeGssContextHandle? context,
SafeGssCredHandle credential,
ReadOnlySpan<byte> buffer,
out byte[] outputBuffer,
out uint outFlags,
out bool isNtlmUsed)
{
Debug.Assert(credential != null);

bool newContext = false;
if (context == null)
{
newContext = true;
context = new SafeGssContextHandle();
}

Interop.NetSecurityNative.GssBuffer token = default(Interop.NetSecurityNative.GssBuffer);
Interop.NetSecurityNative.Status status;

try
{
Interop.NetSecurityNative.Status minorStatus;
status = Interop.NetSecurityNative.AcceptSecContext(out minorStatus,
credential,
ref context,
buffer,
ref token,
out outFlags,
out isNtlmUsed);

if ((status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE) &&
(status != Interop.NetSecurityNative.Status.GSS_S_CONTINUE_NEEDED))
{
if (newContext)
{
context.Dispose();
context = null;
}
throw new Interop.NetSecurityNative.GssApiException(status, minorStatus);
}

outputBuffer = token.ToByteArray();
}
finally
{
token.Dispose();
}

return status == Interop.NetSecurityNative.Status.GSS_S_COMPLETE;
}

private static string GssGetUser(
ref SafeGssContextHandle? context)
{
Expand Down Expand Up @@ -287,27 +147,70 @@ private static SecurityStatusPal EstablishSecurityContext(
context = new SafeDeleteNegoContext(credential, targetName!);
}

Interop.NetSecurityNative.GssBuffer token = default(Interop.NetSecurityNative.GssBuffer);
Interop.NetSecurityNative.Status status;
Interop.NetSecurityNative.Status minorStatus;
SafeDeleteNegoContext negoContext = (SafeDeleteNegoContext)context;
SafeGssContextHandle contextHandle = negoContext.GssContext;
try
{
Interop.NetSecurityNative.GssFlags inputFlags =
ContextFlagsAdapterPal.GetInteropFromContextFlagsPal(inFlags, isServer: false);
uint outputFlags;
bool isNtlmUsed;
SafeGssContextHandle? contextHandle = negoContext.GssContext;
bool done = GssInitSecurityContext(
ref contextHandle,
credential.GssCredential,
isNtlmOnly,
channelBinding,
negoContext.TargetName,
inputFlags,
incomingBlob,
out resultBuffer,
out outputFlags,
out isNtlmUsed);

if (done)

if (channelBinding != null)
{
// If a TLS channel binding token (cbt) is available then get the pointer
// to the application specific data.
int appDataOffset = Marshal.SizeOf<SecChannelBindings>();
Debug.Assert(appDataOffset < channelBinding.Size);
IntPtr cbtAppData = channelBinding.DangerousGetHandle() + appDataOffset;
int cbtAppDataSize = channelBinding.Size - appDataOffset;
status = Interop.NetSecurityNative.InitSecContext(out minorStatus,
credential.GssCredential,
ref contextHandle,
isNtlmOnly,
cbtAppData,
cbtAppDataSize,
negoContext.TargetName,
(uint)inputFlags,
incomingBlob,
ref token,
out outputFlags,
out isNtlmUsed);
}
else
{
status = Interop.NetSecurityNative.InitSecContext(out minorStatus,
credential.GssCredential,
ref contextHandle,
isNtlmOnly,
negoContext.TargetName,
(uint)inputFlags,
incomingBlob,
ref token,
out outputFlags,
out isNtlmUsed);
}

if ((status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE) &&
(status != Interop.NetSecurityNative.Status.GSS_S_CONTINUE_NEEDED))
{
if (negoContext.GssContext.IsInvalid)
{
context.Dispose();
}

Interop.NetSecurityNative.GssApiException gex = new Interop.NetSecurityNative.GssApiException(status, minorStatus);
if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, gex);
resultBuffer = Array.Empty<byte>();
return new SecurityStatusPal(GetErrorCode(gex), gex);
}

resultBuffer = token.ToByteArray();

if (status == Interop.NetSecurityNative.Status.GSS_S_COMPLETE)
{
if (NetEventSource.Log.IsEnabled())
{
Expand All @@ -322,17 +225,9 @@ private static SecurityStatusPal EstablishSecurityContext(
Debug.Assert(resultBuffer != null, "Unexpected null buffer returned by GssApi");
outFlags = ContextFlagsAdapterPal.GetContextFlagsPalFromInterop(
(Interop.NetSecurityNative.GssFlags)outputFlags, isServer: false);
Debug.Assert(negoContext.GssContext == null || contextHandle == negoContext.GssContext);

// Save the inner context handle for further calls to NetSecurity
Debug.Assert(negoContext.GssContext == null || contextHandle == negoContext.GssContext);
if (null == negoContext.GssContext)
{
negoContext.SetGssContext(contextHandle!);
}

SecurityStatusPalErrorCode errorCode = done ?
(negoContext.IsNtlmUsed && resultBuffer.Length > 0 ? SecurityStatusPalErrorCode.OK : SecurityStatusPalErrorCode.CompleteNeeded) :
SecurityStatusPalErrorCode errorCode = status == Interop.NetSecurityNative.Status.GSS_S_COMPLETE ?
SecurityStatusPalErrorCode.OK :
SecurityStatusPalErrorCode.ContinueNeeded;
return new SecurityStatusPal(errorCode);
}
Expand All @@ -341,6 +236,22 @@ private static SecurityStatusPal EstablishSecurityContext(
if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, ex);
return new SecurityStatusPal(SecurityStatusPalErrorCode.InternalError, ex);
}
finally
{
token.Dispose();

// Save the inner context handle for further calls to NetSecurity
//
// For the first call `negoContext.GssContext` is invalid and we expect the
// inital handle to be returned from AcceptSecContext. For any subsequent
// call the handle should stay the same or it can be destroyed by the native
// AcceptSecContext call.
Debug.Assert(
negoContext.GssContext == contextHandle ||
negoContext.GssContext.IsInvalid ||
contextHandle.IsInvalid);
negoContext.SetGssContext(contextHandle);
}
}

internal static SecurityStatusPal InitializeSecurityContext(
Expand Down Expand Up @@ -388,33 +299,44 @@ internal static SecurityStatusPal AcceptSecurityContext(
securityContext ??= new SafeDeleteNegoContext((SafeFreeNegoCredentials)credentialsHandle!);

SafeDeleteNegoContext negoContext = (SafeDeleteNegoContext)securityContext;
SafeGssContextHandle contextHandle = negoContext.GssContext;
Interop.NetSecurityNative.GssBuffer token = default(Interop.NetSecurityNative.GssBuffer);
try
{
SafeGssContextHandle? contextHandle = negoContext.GssContext;
bool done = GssAcceptSecurityContext(
ref contextHandle,
negoContext.AcceptorCredential,
incomingBlob,
out resultBlob,
out uint outputFlags,
out bool isNtlmUsed);

Debug.Assert(resultBlob != null, "Unexpected null buffer returned by GssApi");
Debug.Assert(negoContext.GssContext == null || contextHandle == negoContext.GssContext);
Interop.NetSecurityNative.Status status;
Interop.NetSecurityNative.Status minorStatus;
status = Interop.NetSecurityNative.AcceptSecContext(out minorStatus,
negoContext.AcceptorCredential,
ref contextHandle,
incomingBlob,
ref token,
out uint outputFlags,
out bool isNtlmUsed);

// Save the inner context handle for further calls to NetSecurity
Debug.Assert(negoContext.GssContext == null || contextHandle == negoContext.GssContext);
if (null == negoContext.GssContext)
if ((status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE) &&
(status != Interop.NetSecurityNative.Status.GSS_S_CONTINUE_NEEDED))
{
negoContext.SetGssContext(contextHandle!);
if (negoContext.GssContext.IsInvalid)
{
contextHandle.Dispose();
}

Interop.NetSecurityNative.GssApiException gex = new Interop.NetSecurityNative.GssApiException(status, minorStatus);
if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, gex);
resultBlobLength = 0;
return new SecurityStatusPal(GetErrorCode(gex), gex);
}

resultBlob = token.ToByteArray();

Debug.Assert(resultBlob != null, "Unexpected null buffer returned by GssApi");

contextFlags = ContextFlagsAdapterPal.GetContextFlagsPalFromInterop(
(Interop.NetSecurityNative.GssFlags)outputFlags, isServer: true);
resultBlobLength = resultBlob.Length;

SecurityStatusPalErrorCode errorCode;
if (done)
if (status == Interop.NetSecurityNative.Status.GSS_S_COMPLETE)
{
if (NetEventSource.Log.IsEnabled())
{
Expand All @@ -423,7 +345,7 @@ internal static SecurityStatusPal AcceptSecurityContext(
}

negoContext.SetAuthenticationPackage(isNtlmUsed);
errorCode = (isNtlmUsed && resultBlob.Length > 0) ? SecurityStatusPalErrorCode.OK : SecurityStatusPalErrorCode.CompleteNeeded;
errorCode = SecurityStatusPalErrorCode.OK;
}
else
{
Expand All @@ -432,18 +354,28 @@ internal static SecurityStatusPal AcceptSecurityContext(

return new SecurityStatusPal(errorCode);
}
catch (Interop.NetSecurityNative.GssApiException gex)
{
if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, gex);
resultBlobLength = 0;
return new SecurityStatusPal(GetErrorCode(gex), gex);
}
catch (Exception ex)
{
if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, ex);
resultBlobLength = 0;
return new SecurityStatusPal(SecurityStatusPalErrorCode.InternalError, ex);
}
finally
{
token.Dispose();

// Save the inner context handle for further calls to NetSecurity
//
// For the first call `negoContext.GssContext` is invalid and we expect the
// inital handle to be returned from AcceptSecContext. For any subsequent
// call the handle should stay the same or it can be destroyed by the native
// AcceptSecContext call.
Debug.Assert(
negoContext.GssContext == contextHandle ||
negoContext.GssContext.IsInvalid ||
contextHandle.IsInvalid);
negoContext.SetGssContext(contextHandle);
}
}

// https://www.gnu.org/software/gss/reference/gss.pdf (page 25)
Expand Down
Loading

0 comments on commit 00b2786

Please sign in to comment.