// Returns true if the address table is already stable. Otherwise, calls callback when it becomes stable. // 'Unsafe' because it does not flow ExecutionContext to the callback. public static bool UnsafeNotifyStableUnicastIpAddressTable(Action <object> callback, object state) { if (callback == null) { NetEventSource.Fail(null, "UnsafeNotifyStableUnicastIpAddressTable called without specifying callback!"); } TeredoHelper helper = new TeredoHelper(callback, state); uint err = Interop.IpHlpApi.ERROR_SUCCESS; SafeFreeMibTable table = null; lock (s_pendingNotifications) { // If OnAppDomainUnload gets the lock first, tell our caller that we'll finish async. Their AppDomain // is about to go down anyways. If we do, hold the lock until we've added helper to the // s_pendingNotifications list (if we're going to complete asynchronously). if (Environment.HasShutdownStarted) { return(false); } err = Interop.IpHlpApi.NotifyStableUnicastIpAddressTable(AddressFamily.Unspecified, out table, helper._onStabilizedDelegate, IntPtr.Zero, out helper._cancelHandle); if (table != null) { table.Dispose(); } if (err == Interop.IpHlpApi.ERROR_IO_PENDING) { if (helper._cancelHandle.IsInvalid) { NetEventSource.Fail(null, "CancelHandle invalid despite returning ERROR_IO_PENDING"); } // Async completion: add us to the s_pendingNotifications list so we'll be canceled in the // event of an AppDomain unload. s_pendingNotifications.Add(helper); return(false); } } if (err != Interop.IpHlpApi.ERROR_SUCCESS) { throw new Win32Exception((int)err); } return(true); }
private bool CloseHandle(bool abortive, bool canceledOperations) { bool ret = false; #if DEBUG try { #endif if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"handle:{handle}"); } canceledOperations |= OnHandleClose(); // In case we cancel operations, switch to an abortive close. // Unless the user requested a normal close using Socket.Shutdown. if (canceledOperations && !_hasShutdownSend) { abortive = true; } SocketError errorCode = DoCloseHandle(abortive); return(ret = errorCode == SocketError.Success); #if DEBUG } catch (Exception exception) { if (!ExceptionCheck.IsFatal(exception)) { NetEventSource.Fail(this, $"handle:{handle}, error:{exception}"); } ret = true; // Avoid a second assert. throw; } finally { _closeSocketThread = Environment.CurrentManagedThreadId; _closeSocketTick = Environment.TickCount; if (!ret) { NetEventSource.Fail(this, $"ReleaseHandle failed. handle:{handle}"); } } #endif }
internal static int VerifySignature(SafeDeleteContext securityContext, byte[] buffer, int offset, int count) { if (offset < 0 || offset > (buffer == null ? 0 : buffer.Length)) { NetEventSource.Fail(securityContext, "Argument 'offset' out of range"); throw new ArgumentOutOfRangeException(nameof(offset)); } if (count < 0 || count > (buffer == null ? 0 : buffer.Length - offset)) { NetEventSource.Fail(securityContext, "Argument 'count' out of range."); throw new ArgumentOutOfRangeException(nameof(count)); } return(GssUnwrap(((SafeDeleteNegoContext)securityContext).GssContext, buffer !, offset, count)); }
public SecurityBuffer(byte[] data, int offset, int size, SecurityBufferType tokentype) { if (offset < 0 || offset > (data == null ? 0 : data.Length)) { NetEventSource.Fail(this, $"'offset' out of range. [{offset}]"); } if (size < 0 || size > (data == null ? 0 : data.Length - offset)) { NetEventSource.Fail(this, $"'size' out of range. [{size}]"); } this.offset = data == null || offset < 0 ? 0 : Math.Min(offset, data.Length); this.size = data == null || size < 0 ? 0 : Math.Min(size, data.Length - this.offset); this.type = tokentype; this.token = size == 0 ? null : data; }
internal void CloseAsIs(bool abortive) { #if DEBUG // If this throws it could be very bad. try { #endif bool shouldClose = TryOwnClose(); if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"shouldClose={shouldClose}"); } Dispose(); if (shouldClose) { bool canceledOperations = false; // Wait until it's safe. SpinWait sw = default; while (!_released) { // The socket was not released due to the SafeHandle being used. // Try to make those on-going calls return. // On Linux, TryUnblockSocket will unblock current operations but it doesn't prevent // a new one from starting. So we must call TryUnblockSocket multiple times. canceledOperations |= TryUnblockSocket(abortive); sw.SpinOnce(); } CloseHandle(abortive, canceledOperations); } #if DEBUG } catch (Exception exception) when(!ExceptionCheck.IsFatal(exception)) { NetEventSource.Fail(this, $"handle:{handle}, error:{exception}"); throw; } #endif }
// Called by Socket to kick off the ConnectAsync process. We'll complete the user's SAEA when it's done. // Returns true if the operation is pending, false if it completed synchronously. public bool StartConnectAsync(SocketAsyncEventArgs args, DnsEndPoint endPoint) { IAsyncResult result; Debug.Assert(!Monitor.IsEntered(_lockObject)); lock (_lockObject) { if (endPoint.AddressFamily != AddressFamily.Unspecified && endPoint.AddressFamily != AddressFamily.InterNetwork && endPoint.AddressFamily != AddressFamily.InterNetworkV6) { NetEventSource.Fail(this, $"Unexpected endpoint address family: {endPoint.AddressFamily}"); } _userArgs = args; _endPoint = endPoint; // If Cancel() was called before we got the lock, it only set the state to Canceled: we need to // fail synchronously from here. Once State.DnsQuery is set, the Cancel() call will handle calling AsyncFail. if (_state == State.Canceled) { SyncFail(new SocketException((int)SocketError.OperationAborted)); return(false); } if (_state != State.NotStarted) { NetEventSource.Fail(this, "MultipleConnectAsync.StartConnectAsync(): Unexpected object state"); } _state = State.DnsQuery; result = Dns.BeginGetHostAddresses(endPoint.Host, new AsyncCallback(DnsCallback), null); } if (result.CompletedSynchronously) { return(DoDnsCallback(result, true)); } else { return(true); } }
internal void CloseAsIs(bool abortive) { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"_innerSocket={_innerSocket}"); } #if DEBUG // If this throws it could be very bad. try { #endif InnerSafeCloseSocket innerSocket = _innerSocket == null ? null : Interlocked.Exchange <InnerSafeCloseSocket>(ref _innerSocket, null); Dispose(); if (innerSocket != null) { // Wait until it's safe. SpinWait sw = new SpinWait(); while (!_released) { // The socket was not released due to the SafeHandle being used. // Try to make those on-going calls return. // On Linux, TryUnblockSocket will unblock current operations but it doesn't prevent // a new one from starting. So we must call TryUnblockSocket multiple times. abortive |= innerSocket.TryUnblockSocket(abortive); sw.SpinOnce(); } abortive |= DoReleaseHandle(); innerSocket.Close(abortive); } #if DEBUG } catch (Exception exception) when(!ExceptionCheck.IsFatal(exception)) { NetEventSource.Fail(this, $"handle:{handle}, error:{exception}"); throw; } #endif }
private static void WriteCallback(IAsyncResult transportResult) { if (transportResult.CompletedSynchronously) { return; } if (!(transportResult.AsyncState is AsyncProtocolRequest)) { NetEventSource.Fail(transportResult, "State type is wrong, expected AsyncProtocolRequest."); } AsyncProtocolRequest asyncRequest = (AsyncProtocolRequest)transportResult.AsyncState; var sslStream = (SslStreamInternal)asyncRequest.AsyncObject; try { TaskToApm.End(transportResult); sslStream._sslState.FinishWrite(); if (asyncRequest.Count == 0) { // This was the last chunk. asyncRequest.Count = -1; } sslStream.StartWriting(asyncRequest.Buffer, asyncRequest.Offset, asyncRequest.Count, asyncRequest); } catch (Exception e) { if (asyncRequest.IsUserCompleted) { // This will throw on a worker thread. throw; } sslStream._sslState.FinishWrite(); asyncRequest.CompleteUserWithError(e); } }
internal void CancelConnectAsync() { if (_operating == InProgress && _completedOperation == SocketAsyncOperation.Connect) { if (_multipleConnect != null) { // If a multiple connect is in progress, abort it. _multipleConnect.Cancel(); } else { // Otherwise we're doing a normal ConnectAsync - cancel it by closing the socket. // _currentSocket will only be null if _multipleConnect was set, so we don't have to check. if (_currentSocket == null) { NetEventSource.Fail(this, "CurrentSocket and MultipleConnect both null!"); } _currentSocket.Dispose(); } } }
internal void CloseAsIs() { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"_innerSocket={_innerSocket}"); } #if DEBUG // If this throws it could be very bad. try { #endif InnerSafeCloseSocket innerSocket = _innerSocket == null ? null : Interlocked.Exchange <InnerSafeCloseSocket>(ref _innerSocket, null); Dispose(); if (innerSocket != null) { // Wait until it's safe. SpinWait sw = new SpinWait(); while (!_released) { sw.SpinOnce(); } InnerReleaseHandle(); // Now free it with blocking. innerSocket.BlockingRelease(); } #if DEBUG } catch (Exception exception) when(!ExceptionCheck.IsFatal(exception)) { NetEventSource.Fail(this, $"handle:{handle}, error:{exception}"); throw; } #endif }
// This method is implicitly reliable and called from a CER. protected override bool ReleaseHandle() { bool ret = false; #if DEBUG try { #endif if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"handle:{handle}"); } SocketError errorCode = InnerReleaseHandle(); return(ret = errorCode == SocketError.Success); #if DEBUG } catch (Exception exception) { if (!ExceptionCheck.IsFatal(exception)) { NetEventSource.Fail(this, $"handle:{handle}, error:{exception}"); } ret = true; // Avoid a second assert. throw; } finally { _closeSocketThread = Environment.CurrentManagedThreadId; _closeSocketTick = Environment.TickCount; if (!ret) { NetEventSource.Fail(this, $"ReleaseHandle failed. handle:{handle}"); } } #endif }
private static void WriteCallback(IAsyncResult transportResult) { if (!(transportResult.AsyncState is LazyAsyncResult)) { NetEventSource.Fail(transportResult, "State type is wrong, expected LazyAsyncResult."); } if (transportResult.CompletedSynchronously) { return; } LazyAsyncResult lazyResult = (LazyAsyncResult)transportResult.AsyncState; // Async completion. try { NegoState authState = (NegoState)lazyResult.AsyncObject; authState._framer.EndWriteMessage(transportResult); // Special case for an error notification. if (lazyResult.Result is Exception e) { authState._canRetryAuthentication = true; ExceptionDispatchInfo.Throw(e); } authState.CheckCompletionBeforeNextReceive(lazyResult); } catch (Exception e) { if (lazyResult.InternalPeekCompleted) { // This will throw on a worker thread. throw; } lazyResult.InvokeCallback(e); } }
// // To avoid recursion when decrypted 0 bytes this method will loop until a decrypted result at least 1 byte. // private int StartReading(byte[] buffer, int offset, int count, AsyncProtocolRequest asyncRequest) { int result = 0; if (InternalBufferCount != 0) { NetEventSource.Fail(this, $"Previous frame was not consumed. InternalBufferCount: {InternalBufferCount}"); } do { if (asyncRequest != null) { asyncRequest.SetNextRequest(buffer, offset, count, s_resumeAsyncReadCallback); } int copyBytes = _sslState.CheckEnqueueRead(buffer, offset, count, asyncRequest); if (copyBytes == 0) { // Queued but not completed! return(0); } if (copyBytes != -1) { if (asyncRequest != null) { asyncRequest.CompleteUser((object)copyBytes); } return(copyBytes); } } // When we read -1 bytes means we have decrypted 0 bytes or rehandshaking, need looping. while ((result = StartFrameHeader(buffer, offset, count, asyncRequest)) == -1); return(result); }
private Exception AttemptUserConnection() { Debug.Assert(!SocketPal.SupportsMultipleConnectAttempts); Debug.Assert(_lastAttemptSocket != null); try { _lastAttemptSocket.Dispose(); // Setup the internal args. RemoteEndpoint should already be correct. _internalArgs.SetBuffer(_userArgs.Buffer, _userArgs.Offset, _userArgs.Count); return(AttemptConnection(UserSocket, _internalArgs)); } catch (Exception e) { if (e is ObjectDisposedException) { NetEventSource.Fail(this, "unexpected ObjectDisposedException"); } return(e); } }
private static void WriteCallback(IAsyncResult transportResult) { if (transportResult.CompletedSynchronously) { return; } if (!(transportResult.AsyncState is AsyncProtocolRequest)) { NetEventSource.Fail(transportResult, "State type is wrong, expected AsyncProtocolRequest."); } AsyncProtocolRequest asyncRequest = (AsyncProtocolRequest)transportResult.AsyncState; try { NegotiateStream negoStream = (NegotiateStream)asyncRequest.AsyncObject; negoStream.InnerStream.EndWrite(transportResult); if (asyncRequest.Count == 0) { // This was the last chunk. asyncRequest.Count = -1; } negoStream.StartWriting(asyncRequest.Buffer, asyncRequest.Offset, asyncRequest.Count, asyncRequest); } catch (Exception e) { if (asyncRequest.IsUserCompleted) { // This will throw on a worker thread. throw; } asyncRequest.CompleteWithError(e); } }
private Authorization SetContextAndTryAuthenticate(ISmtpAuthenticationModule module, NetworkCredential credential, ContextAwareResult context) { // We may need to restore user thread token here if (ReferenceEquals(credential, CredentialCache.DefaultNetworkCredentials)) { #if DEBUG if (context != null && !context.IdentityRequested) { NetEventSource.Fail(this, "Authentication required when it wasn't expected. (Maybe Credentials was changed on another thread?)"); } #endif try { ExecutionContext x = context == null ? null : context.ContextCopy; if (x != null) { AuthenticateCallbackContext authenticationContext = new AuthenticateCallbackContext(this, module, credential, _client.TargetName, _channelBindingToken); ExecutionContext.Run(x, s_AuthenticateCallback, authenticationContext); return(authenticationContext._result); } else { return(module.Authenticate(null, credential, this, _client.TargetName, _channelBindingToken)); } } catch { // Prevent the impersonation from leaking to upstack exception filters. throw; } } return(module.Authenticate(null, credential, this, _client.TargetName, _channelBindingToken)); }
private void RunCallback(object o) { if (!_runCallbackCalled) { NetEventSource.Fail(null, "RunCallback called without setting runCallbackCalled!"); } // If OnAppDomainUnload beats us to the lock, do nothing: the AppDomain is going down soon anyways. // Otherwise, wait until the call to CancelMibChangeNotify2 is done before giving it up. lock (s_pendingNotifications) { if (Environment.HasShutdownStarted) { return; } #if DEBUG bool successfullyRemoved = s_pendingNotifications.Remove(this); if (!successfullyRemoved) { NetEventSource.Fail(null, "RunCallback for a TeredoHelper which is not in s_pendingNotifications!"); } #else s_pendingNotifications.Remove(this); #endif if ((_cancelHandle == null || _cancelHandle.IsInvalid)) { NetEventSource.Fail(null, "Invalid cancelHandle in RunCallback"); } _cancelHandle.Dispose(); } _callback.Invoke(_state); }
internal static int Decrypt( SafeDeleteContext securityContext, byte[] buffer, int offset, int count, bool isConfidential, bool isNtlm, out int newOffset, uint sequenceNumber) { if (offset < 0 || offset > (buffer == null ? 0 : buffer.Length)) { NetEventSource.Fail(null, "Argument 'offset' out of range."); throw new ArgumentOutOfRangeException(nameof(offset)); } if (count < 0 || count > (buffer == null ? 0 : buffer.Length - offset)) { NetEventSource.Fail(null, "Argument 'count' out of range."); throw new ArgumentOutOfRangeException(nameof(count)); } if (isNtlm) { return(DecryptNtlm(securityContext, buffer, offset, count, isConfidential, out newOffset, sequenceNumber)); } // // Kerberos and up // TwoSecurityBuffers buffers = default; var securityBuffer = MemoryMarshal.CreateSpan(ref buffers._item0, 2); securityBuffer[0] = new SecurityBuffer(buffer, offset, count, SecurityBufferType.SECBUFFER_STREAM); securityBuffer[1] = new SecurityBuffer(0, SecurityBufferType.SECBUFFER_DATA); int errorCode; if (isConfidential) { errorCode = SSPIWrapper.DecryptMessage(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, sequenceNumber); } else { errorCode = SSPIWrapper.VerifySignature(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, sequenceNumber); } if (errorCode != 0) { Exception e = new Win32Exception(errorCode); if (NetEventSource.IsEnabled) { NetEventSource.Error(null, e); } throw e; } if (securityBuffer[1].type != SecurityBufferType.SECBUFFER_DATA) { throw new InternalException(securityBuffer[1].type); } newOffset = securityBuffer[1].offset; return(securityBuffer[1].size); }
public static unsafe SecurityStatusPal EncryptMessage(SafeDeleteContext securityContext, ReadOnlyMemory <byte> input, int headerSize, int trailerSize, ref byte[] output, out int resultSize) { // Ensure that there is sufficient space for the message output. int bufferSizeNeeded; try { bufferSizeNeeded = checked (input.Length + headerSize + trailerSize); } catch { NetEventSource.Fail(securityContext, "Arguments out of range"); throw; } if (output == null || output.Length < bufferSizeNeeded) { output = new byte[bufferSizeNeeded]; } // Copy the input into the output buffer to prepare for SCHANNEL's expectations input.Span.CopyTo(new Span <byte>(output, headerSize, input.Length)); const int NumSecBuffers = 4; // header + data + trailer + empty var unmanagedBuffer = stackalloc Interop.SspiCli.SecBuffer[NumSecBuffers]; var sdcInOut = new Interop.SspiCli.SecBufferDesc(NumSecBuffers); sdcInOut.pBuffers = unmanagedBuffer; fixed(byte *outputPtr = output) { Interop.SspiCli.SecBuffer *headerSecBuffer = &unmanagedBuffer[0]; headerSecBuffer->BufferType = SecurityBufferType.SECBUFFER_STREAM_HEADER; headerSecBuffer->pvBuffer = (IntPtr)outputPtr; headerSecBuffer->cbBuffer = headerSize; Interop.SspiCli.SecBuffer *dataSecBuffer = &unmanagedBuffer[1]; dataSecBuffer->BufferType = SecurityBufferType.SECBUFFER_DATA; dataSecBuffer->pvBuffer = (IntPtr)(outputPtr + headerSize); dataSecBuffer->cbBuffer = input.Length; Interop.SspiCli.SecBuffer *trailerSecBuffer = &unmanagedBuffer[2]; trailerSecBuffer->BufferType = SecurityBufferType.SECBUFFER_STREAM_TRAILER; trailerSecBuffer->pvBuffer = (IntPtr)(outputPtr + headerSize + input.Length); trailerSecBuffer->cbBuffer = trailerSize; Interop.SspiCli.SecBuffer *emptySecBuffer = &unmanagedBuffer[3]; emptySecBuffer->BufferType = SecurityBufferType.SECBUFFER_EMPTY; emptySecBuffer->cbBuffer = 0; emptySecBuffer->pvBuffer = IntPtr.Zero; int errorCode = GlobalSSPI.SSPISecureChannel.EncryptMessage(securityContext, ref sdcInOut, 0); if (errorCode != 0) { if (NetEventSource.IsEnabled) { NetEventSource.Info(securityContext, $"Encrypt ERROR {errorCode:X}"); } resultSize = 0; return(SecurityStatusAdapterPal.GetSecurityStatusPalFromNativeInt(errorCode)); } Debug.Assert(headerSecBuffer->cbBuffer >= 0 && dataSecBuffer->cbBuffer >= 0 && trailerSecBuffer->cbBuffer >= 0); Debug.Assert(checked (headerSecBuffer->cbBuffer + dataSecBuffer->cbBuffer + trailerSecBuffer->cbBuffer) <= output.Length); resultSize = checked (headerSecBuffer->cbBuffer + dataSecBuffer->cbBuffer + trailerSecBuffer->cbBuffer); return(new SecurityStatusPal(SecurityStatusPalErrorCode.OK)); } }
// Callback which fires when an internal connection attempt completes. // If it failed and there are more addresses to try, do it. private void InternalConnectCallback(object sender, SocketAsyncEventArgs args) { Exception exception = null; lock (_lockObject) { if (_state == State.Canceled) { // If Cancel was called before we got the lock, the Socket will be closed soon. We need to report // OperationAborted (even though the connection actually completed), or the user will try to use a // closed Socket. exception = new SocketException((int)SocketError.OperationAborted); } else if (_state == State.ConnectAttempt) { if (args.SocketError == SocketError.Success) { if (RequiresUserConnectAttempt) { exception = AttemptUserConnection(); if (exception == null) { // Don't call the callback; we've started a connection attempt on the // user's socket. _state = State.UserConnectAttempt; return; } } // The connection attempt succeeded or the user connect attempt failed synchronously; go to the // completed state. The callback will be called outside the lock. _state = State.Completed; } else if (args.SocketError == SocketError.OperationAborted) { // The socket was closed while the connect was in progress. This can happen if the user // closes the socket, and is equivalent to a call to CancelConnectAsync exception = new SocketException((int)SocketError.OperationAborted); _state = State.Canceled; } else { // Try again, if there are more IPAddresses to be had. // If the underlying OS does not support multiple connect attempts // on the same socket, dispose the socket used for the last attempt. if (!SocketPal.SupportsMultipleConnectAttempts) { _lastAttemptSocket.Dispose(); } // Keep track of this because it will be overwritten by AttemptConnection SocketError currentFailure = args.SocketError; Exception connectException = AttemptConnection(); if (connectException == null) { // don't call the callback, another connection attempt is successfully started return; } else { SocketException socketException = connectException as SocketException; if (socketException != null && socketException.SocketErrorCode == SocketError.NoData) { // If the error is NoData, that means there are no more IPAddresses to attempt // a connection to. Return the last error from an actual connection instead. exception = new SocketException((int)currentFailure); } else { exception = connectException; } _state = State.Completed; } } } else { if (_state != State.UserConnectAttempt) { NetEventSource.Fail(this, "Unexpected object state"); } if (!RequiresUserConnectAttempt) { NetEventSource.Fail(this, "State.UserConnectAttempt without RequiresUserConnectAttempt"); } if (args.SocketError == SocketError.Success) { _state = State.Completed; } else if (args.SocketError == SocketError.OperationAborted) { // The socket was closed while the connect was in progress. This can happen if the user // closes the socket, and is equivalent to a call to CancelConnectAsync exception = new SocketException((int)SocketError.OperationAborted); _state = State.Canceled; } else { // The connect attempt on the user's socket failed. Return the corresponding error. exception = new SocketException((int)args.SocketError); _state = State.Completed; } } } if (exception == null) { Succeed(); } else { AsyncFail(exception); } }
private static unsafe void CompletionPortCallback(uint errorCode, uint numBytes, NativeOverlapped *nativeOverlapped) { #if DEBUG DebugThreadTracking.SetThreadSource(ThreadKinds.CompletionPort); using (DebugThreadTracking.SetThreadKind(ThreadKinds.System)) { #endif BaseOverlappedAsyncResult asyncResult = (BaseOverlappedAsyncResult)ThreadPoolBoundHandle.GetNativeOverlappedState(nativeOverlapped); object returnObject = null; if (asyncResult.InternalPeekCompleted) { NetEventSource.Fail(null, $"asyncResult.IsCompleted: {asyncResult}"); } if (NetEventSource.IsEnabled) { NetEventSource.Info(null, $"errorCode:{errorCode} numBytes:{numBytes} nativeOverlapped:{(IntPtr)nativeOverlapped}"); } // Complete the IO and invoke the user's callback. SocketError socketError = (SocketError)errorCode; if (socketError != SocketError.Success && socketError != SocketError.OperationAborted) { // There are cases where passed errorCode does not reflect the details of the underlined socket error. // "So as of today, the key is the difference between WSAECONNRESET and ConnectionAborted, // .e.g remote party or network causing the connection reset or something on the local host (e.g. closesocket // or receiving data after shutdown (SD_RECV)). With Winsock/TCP stack rewrite in longhorn, there may // be other differences as well." Socket socket = asyncResult.AsyncObject as Socket; if (socket == null) { socketError = SocketError.NotSocket; } else if (socket.CleanedUp) { socketError = SocketError.OperationAborted; } else { try { // The async IO completed with a failure. // Here we need to call WSAGetOverlappedResult() just so Marshal.GetLastWin32Error() will return the correct error. SocketFlags ignore; bool success = Interop.Winsock.WSAGetOverlappedResult( socket.SafeHandle, asyncResult.NativeOverlapped, out numBytes, false, out ignore); if (!success) { socketError = (SocketError)Marshal.GetLastWin32Error(); if (socketError == 0) { NetEventSource.Fail(asyncResult, $"socketError:0 numBytes:{numBytes}"); } } if (success) { NetEventSource.Fail(asyncResult, $"Unexpectedly succeeded. errorCode:{errorCode} numBytes:{numBytes}"); } } catch (ObjectDisposedException) { // CleanedUp check above does not always work since this code is subject to race conditions socketError = SocketError.OperationAborted; } } } asyncResult.ErrorCode = (int)socketError; returnObject = asyncResult.PostCompletion((int)numBytes); asyncResult.ReleaseUnmanagedStructures(); asyncResult.InvokeCallback(returnObject); #if DEBUG } #endif }
private async ValueTask <int> ReadAsyncInternal <TIOAdapter>(TIOAdapter adapter, Memory <byte> buffer) where TIOAdapter : ISslIOAdapter { if (Interlocked.Exchange(ref _nestedRead, 1) == 1) { throw new NotSupportedException(SR.Format(SR.net_io_invalidnestedcall, nameof(SslStream.ReadAsync), "read")); } try { while (true) { int copyBytes; if (_decryptedBytesCount != 0) { copyBytes = CopyDecryptedData(buffer); return(copyBytes); } copyBytes = await adapter.ReadLockAsync(buffer).ConfigureAwait(false); if (copyBytes > 0) { return(copyBytes); } ResetReadBuffer(); int readBytes = await FillBufferAsync(adapter, SecureChannel.ReadHeaderSize).ConfigureAwait(false); if (readBytes == 0) { return(0); } int payloadBytes = GetRemainingFrameSize(_internalBuffer, _internalOffset, readBytes); if (payloadBytes < 0) { throw new IOException(SR.net_frame_read_size); } readBytes = await FillBufferAsync(adapter, SecureChannel.ReadHeaderSize + payloadBytes).ConfigureAwait(false); Debug.Assert(readBytes >= 0); if (readBytes == 0) { throw new IOException(SR.net_io_eof); } // At this point, readBytes contains the size of the header plus body. // Set _decrytpedBytesOffset/Count to the current frame we have (including header) // DecryptData will decrypt in-place and modify these to point to the actual decrypted data, which may be smaller. _decryptedBytesOffset = _internalOffset; _decryptedBytesCount = readBytes; SecurityStatusPal status = DecryptData(); // Treat the bytes we just decrypted as consumed // Note, we won't do another buffer read until the decrypted bytes are processed ConsumeBufferedBytes(readBytes); if (status.ErrorCode != SecurityStatusPalErrorCode.OK) { byte[] extraBuffer = null; if (_decryptedBytesCount != 0) { extraBuffer = new byte[_decryptedBytesCount]; Buffer.BlockCopy(_internalBuffer, _decryptedBytesOffset, extraBuffer, 0, _decryptedBytesCount); _decryptedBytesCount = 0; } ProtocolToken message = new ProtocolToken(null, status); if (NetEventSource.IsEnabled) { NetEventSource.Info(null, $"***Processing an error Status = {message.Status}"); } if (message.Renegotiate) { if (!_sslAuthenticationOptions.AllowRenegotiation) { if (NetEventSource.IsEnabled) { NetEventSource.Fail(this, "Renegotiation was requested but it is disallowed"); } throw new IOException(SR.net_ssl_io_renego); } await ReplyOnReAuthenticationAsync(adapter, extraBuffer).ConfigureAwait(false); // Loop on read. continue; } if (message.CloseConnection) { return(0); } throw new IOException(SR.net_io_decrypt, message.GetException()); } } } catch (Exception e) { if (e is IOException || (e is OperationCanceledException && adapter.CancellationToken.IsCancellationRequested)) { throw; } throw new IOException(SR.net_io_read, e); } finally { _nestedRead = 0; } }
// We need at least 5 bytes to determine what we have. private Framing DetectFraming(byte[] bytes, int length) { /* PCTv1.0 Hello starts with * RECORD_LENGTH_MSB (ignore) * RECORD_LENGTH_LSB (ignore) * PCT1_CLIENT_HELLO (must be equal) * PCT1_CLIENT_VERSION_MSB (if version greater than PCTv1) * PCT1_CLIENT_VERSION_LSB (if version greater than PCTv1) * * ... PCT hello ... */ /* Microsoft Unihello starts with * RECORD_LENGTH_MSB (ignore) * RECORD_LENGTH_LSB (ignore) * SSL2_CLIENT_HELLO (must be equal) * SSL2_CLIENT_VERSION_MSB (if version greater than SSLv2) ( or v3) * SSL2_CLIENT_VERSION_LSB (if version greater than SSLv2) ( or v3) * * ... SSLv2 Compatible Hello ... */ /* SSLv2 CLIENT_HELLO starts with * RECORD_LENGTH_MSB (ignore) * RECORD_LENGTH_LSB (ignore) * SSL2_CLIENT_HELLO (must be equal) * SSL2_CLIENT_VERSION_MSB (if version greater than SSLv2) ( or v3) * SSL2_CLIENT_VERSION_LSB (if version greater than SSLv2) ( or v3) * * ... SSLv2 CLIENT_HELLO ... */ /* SSLv2 SERVER_HELLO starts with * RECORD_LENGTH_MSB (ignore) * RECORD_LENGTH_LSB (ignore) * SSL2_SERVER_HELLO (must be equal) * SSL2_SESSION_ID_HIT (ignore) * SSL2_CERTIFICATE_TYPE (ignore) * SSL2_CLIENT_VERSION_MSB (if version greater than SSLv2) ( or v3) * SSL2_CLIENT_VERSION_LSB (if version greater than SSLv2) ( or v3) * * ... SSLv2 SERVER_HELLO ... */ /* SSLv3 Type 2 Hello starts with * RECORD_LENGTH_MSB (ignore) * RECORD_LENGTH_LSB (ignore) * SSL2_CLIENT_HELLO (must be equal) * SSL2_CLIENT_VERSION_MSB (if version greater than SSLv3) * SSL2_CLIENT_VERSION_LSB (if version greater than SSLv3) * * ... SSLv2 Compatible Hello ... */ /* SSLv3 Type 3 Hello starts with * 22 (HANDSHAKE MESSAGE) * VERSION MSB * VERSION LSB * RECORD_LENGTH_MSB (ignore) * RECORD_LENGTH_LSB (ignore) * HS TYPE (CLIENT_HELLO) * 3 bytes HS record length * HS Version * HS Version */ /* SSLv2 message codes * SSL_MT_ERROR 0 * SSL_MT_CLIENT_HELLO 1 * SSL_MT_CLIENT_MASTER_KEY 2 * SSL_MT_CLIENT_FINISHED 3 * SSL_MT_SERVER_HELLO 4 * SSL_MT_SERVER_VERIFY 5 * SSL_MT_SERVER_FINISHED 6 * SSL_MT_REQUEST_CERTIFICATE 7 * SSL_MT_CLIENT_CERTIFICATE 8 */ int version = -1; if ((bytes == null || bytes.Length <= 0)) { NetEventSource.Fail(this, "Header buffer is not allocated."); } // If the first byte is SSL3 HandShake, then check if we have a SSLv3 Type3 client hello. if (bytes[0] == (byte)FrameType.Handshake || bytes[0] == (byte)FrameType.AppData || bytes[0] == (byte)FrameType.Alert) { if (length < 3) { return(Framing.Invalid); } #if TRACE_VERBOSE if (bytes[1] != 3 && NetEventSource.IsEnabled) { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"WARNING: SslState::DetectFraming() SSL protocol is > 3, trying SSL3 framing in retail = {bytes[1]:x}"); } } #endif version = (bytes[1] << 8) | bytes[2]; if (version < 0x300 || version >= 0x500) { return(Framing.Invalid); } // // This is an SSL3 Framing // return(Framing.SinceSSL3); } #if TRACE_VERBOSE if ((bytes[0] & 0x80) == 0 && NetEventSource.IsEnabled) { // We have a three-byte header format if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"WARNING: SslState::DetectFraming() SSL v <=2 HELLO has no high bit set for 3 bytes header, we are broken, received byte = {bytes[0]:x}"); } } #endif if (length < 3) { return(Framing.Invalid); } if (bytes[2] > 8) { return(Framing.Invalid); } if (bytes[2] == 0x1) // SSL_MT_CLIENT_HELLO { if (length >= 5) { version = (bytes[3] << 8) | bytes[4]; } } else if (bytes[2] == 0x4) // SSL_MT_SERVER_HELLO { if (length >= 7) { version = (bytes[5] << 8) | bytes[6]; } } if (version != -1) { // If this is the first packet, the client may start with an SSL2 packet // but stating that the version is 3.x, so check the full range. // For the subsequent packets we assume that an SSL2 packet should have a 2.x version. if (_framing == Framing.Unknown) { if (version != 0x0002 && (version < 0x200 || version >= 0x500)) { return(Framing.Invalid); } } else { if (version != 0x0002) { return(Framing.Invalid); } } } // When server has replied the framing is already fixed depending on the prior client packet if (!_context.IsServer || _framing == Framing.Unified) { return(Framing.BeforeSSL3); } return(Framing.Unified); // Will use Ssl2 just for this frame. }
internal static int Encrypt( SafeDeleteContext securityContext, byte[] buffer, int offset, int count, bool isConfidential, bool isNtlm, ref byte[] output, uint sequenceNumber) { SecPkgContext_Sizes sizes = default; bool success = SSPIWrapper.QueryBlittableContextAttributes(GlobalSSPI.SSPIAuth, securityContext, Interop.SspiCli.ContextAttribute.SECPKG_ATTR_SIZES, ref sizes); Debug.Assert(success); try { int maxCount = checked (int.MaxValue - 4 - sizes.cbBlockSize - sizes.cbSecurityTrailer); if (count > maxCount || count < 0) { throw new ArgumentOutOfRangeException(nameof(count), SR.Format(SR.net_io_out_range, maxCount)); } } catch (Exception e) when(!ExceptionCheck.IsFatal(e)) { NetEventSource.Fail(null, "Arguments out of range."); throw; } int resultSize = count + sizes.cbSecurityTrailer + sizes.cbBlockSize; if (output == null || output.Length < resultSize + 4) { output = new byte[resultSize + 4]; } // Make a copy of user data for in-place encryption. Buffer.BlockCopy(buffer, offset, output, 4 + sizes.cbSecurityTrailer, count); // Prepare buffers TOKEN(signature), DATA and Padding. ThreeSecurityBuffers buffers = default; var securityBuffer = MemoryMarshal.CreateSpan(ref buffers._item0, 3); securityBuffer[0] = new SecurityBuffer(output, 4, sizes.cbSecurityTrailer, SecurityBufferType.SECBUFFER_TOKEN); securityBuffer[1] = new SecurityBuffer(output, 4 + sizes.cbSecurityTrailer, count, SecurityBufferType.SECBUFFER_DATA); securityBuffer[2] = new SecurityBuffer(output, 4 + sizes.cbSecurityTrailer + count, sizes.cbBlockSize, SecurityBufferType.SECBUFFER_PADDING); int errorCode; if (isConfidential) { errorCode = SSPIWrapper.EncryptMessage(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, sequenceNumber); } else { if (isNtlm) { securityBuffer[1].type |= SecurityBufferType.SECBUFFER_READONLY; } errorCode = SSPIWrapper.MakeSignature(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, 0); } if (errorCode != 0) { Exception e = new Win32Exception(errorCode); if (NetEventSource.IsEnabled) { NetEventSource.Error(null, e); } throw e; } // Compacting the result. resultSize = securityBuffer[0].size; bool forceCopy = false; if (resultSize != sizes.cbSecurityTrailer) { forceCopy = true; Buffer.BlockCopy(output, securityBuffer[1].offset, output, 4 + resultSize, securityBuffer[1].size); } resultSize += securityBuffer[1].size; if (securityBuffer[2].size != 0 && (forceCopy || resultSize != (count + sizes.cbSecurityTrailer))) { Buffer.BlockCopy(output, securityBuffer[2].offset, output, 4 + resultSize, securityBuffer[2].size); } resultSize += securityBuffer[2].size; unchecked { output[0] = (byte)((resultSize) & 0xFF); output[1] = (byte)(((resultSize) >> 8) & 0xFF); output[2] = (byte)(((resultSize) >> 16) & 0xFF); output[3] = (byte)(((resultSize) >> 24) & 0xFF); } return(resultSize + 4); }
private async ValueTask <int> ReadAsyncInternal <TIOAdapter>(TIOAdapter adapter, Memory <byte> buffer) where TIOAdapter : ISslIOAdapter { if (Interlocked.Exchange(ref _nestedRead, 1) == 1) { throw new NotSupportedException(SR.Format(SR.net_io_invalidnestedcall, nameof(SslStream.ReadAsync), "read")); } try { while (true) { if (_decryptedBytesCount != 0) { return(CopyDecryptedData(buffer)); } int copyBytes = await adapter.ReadLockAsync(buffer).ConfigureAwait(false); if (copyBytes > 0) { return(copyBytes); } ResetReadBuffer(); // Read the next frame header. if (_internalBufferCount < SecureChannel.ReadHeaderSize) { // We don't have enough bytes buffered, so issue an initial read to try to get enough. This is // done in this method both to better consolidate error handling logic (the first read is the special // case that needs to differentiate reading 0 from > 0, and everything else needs to throw if it // doesn't read enough), and to minimize the chances that in the common case the FillBufferAsync // helper needs to yield and allocate a state machine. int readBytes = await adapter.ReadAsync(_internalBuffer.AsMemory(_internalBufferCount)).ConfigureAwait(false); if (readBytes == 0) { return(0); } _internalBufferCount += readBytes; if (_internalBufferCount < SecureChannel.ReadHeaderSize) { await FillBufferAsync(adapter, SecureChannel.ReadHeaderSize).ConfigureAwait(false); } } Debug.Assert(_internalBufferCount >= SecureChannel.ReadHeaderSize); // Parse the frame header to determine the payload size (which includes the header size). int payloadBytes = GetFrameSize(_internalBuffer.AsSpan(_internalOffset)); if (payloadBytes < 0) { throw new IOException(SR.net_frame_read_size); } // Read in the rest of the payload if we don't have it. if (_internalBufferCount < payloadBytes) { await FillBufferAsync(adapter, payloadBytes).ConfigureAwait(false); } // Set _decrytpedBytesOffset/Count to the current frame we have (including header) // DecryptData will decrypt in-place and modify these to point to the actual decrypted data, which may be smaller. _decryptedBytesOffset = _internalOffset; _decryptedBytesCount = payloadBytes; SecurityStatusPal status = DecryptData(); // Treat the bytes we just decrypted as consumed // Note, we won't do another buffer read until the decrypted bytes are processed ConsumeBufferedBytes(payloadBytes); if (status.ErrorCode != SecurityStatusPalErrorCode.OK) { byte[] extraBuffer = null; if (_decryptedBytesCount != 0) { extraBuffer = new byte[_decryptedBytesCount]; Buffer.BlockCopy(_internalBuffer, _decryptedBytesOffset, extraBuffer, 0, _decryptedBytesCount); _decryptedBytesCount = 0; } if (NetEventSource.IsEnabled) { NetEventSource.Info(null, $"***Processing an error Status = {status}"); } if (status.ErrorCode == SecurityStatusPalErrorCode.Renegotiate) { if (!_sslAuthenticationOptions.AllowRenegotiation) { if (NetEventSource.IsEnabled) { NetEventSource.Fail(this, "Renegotiation was requested but it is disallowed"); } throw new IOException(SR.net_ssl_io_renego); } await ReplyOnReAuthenticationAsync(adapter, extraBuffer).ConfigureAwait(false); // Loop on read. continue; } if (status.ErrorCode == SecurityStatusPalErrorCode.ContextExpired) { return(0); } throw new IOException(SR.net_io_decrypt, SslStreamPal.GetException(status)); } } } catch (Exception e) { if (e is IOException || (e is OperationCanceledException && adapter.CancellationToken.IsCancellationRequested)) { throw; } throw new IOException(SR.net_io_read, e); } finally { _nestedRead = 0; } }
// Called when the DNS query completes (either synchronously or asynchronously). Checks for failure and // starts the first connection attempt if it succeeded. Returns true if the operation will be asynchronous, // false if it has failed synchronously. private bool DoDnsCallback(IAsyncResult result, bool sync) { Exception exception = null; lock (_lockObject) { // If the connection attempt was canceled during the dns query, the user's callback has already been // called asynchronously and we simply need to return. if (_state == State.Canceled) { return(true); } if (_state != State.DnsQuery) { NetEventSource.Fail(this, "MultipleConnectAsync.DoDnsCallback(): Unexpected object state"); } try { _addressList = Dns.EndGetHostAddresses(result); if (_addressList == null) { NetEventSource.Fail(this, "MultipleConnectAsync.DoDnsCallback(): EndGetHostAddresses returned null!"); } } catch (Exception e) { _state = State.Completed; exception = e; } // If the dns query succeeded, try to connect to the first address if (exception == null) { _state = State.ConnectAttempt; _internalArgs = new SocketAsyncEventArgs(); _internalArgs.Completed += InternalConnectCallback; _internalArgs.CopyBufferFrom(_userArgs); exception = AttemptConnection(); if (exception != null) { // There was a synchronous error while connecting _state = State.Completed; } } } // Call this outside of the lock because it might call the user's callback. if (exception != null) { return(Fail(sync, exception)); } else { return(true); } }
internal InternalException() { NetEventSource.Fail(this, "InternalException thrown."); }
// // The app is calling this method after starting an SSL handshake. // // ATTN: The thumbPrint must be from inspected and possibly cloned user Cert object or we get a security hole in SslCredKey ctor. // internal static void CacheCredential(SafeFreeCredentials creds, byte[]?thumbPrint, SslProtocols sslProtocols, bool isServer, EncryptionPolicy encryptionPolicy) { if (creds == null) { NetEventSource.Fail(null, "creds == null"); } if (creds !.IsInvalid) { if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(null, $"Refused to cache an Invalid Handle {creds}, Current Cache Count = {s_cachedCreds.Count}"); } return; } var key = new SslCredKey(thumbPrint, (int)sslProtocols, isServer, encryptionPolicy); SafeCredentialReference?cached; if (!s_cachedCreds.TryGetValue(key, out cached) || cached.IsClosed || cached.Target.IsInvalid) { lock (s_cachedCreds) { if (!s_cachedCreds.TryGetValue(key, out cached) || cached.IsClosed) { cached = SafeCredentialReference.CreateReference(creds); if (cached == null) { // Means the handle got closed in between, return it back and let caller deal with the issue. return; } s_cachedCreds[key] = cached; if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(null, $"Caching New Handle = {creds}, Current Cache Count = {s_cachedCreds.Count}"); } // // A simplest way of preventing infinite cache grows. // // Security relief (DoS): // A number of active creds is never greater than a number of _outstanding_ // security sessions, i.e. SSL connections. // So we will try to shrink cache to the number of active creds once in a while. // // We won't shrink cache in the case when NO new handles are coming to it. // if ((s_cachedCreds.Count % CheckExpiredModulo) == 0) { KeyValuePair <SslCredKey, SafeCredentialReference>[] toRemoveAttempt = s_cachedCreds.ToArray(); for (int i = 0; i < toRemoveAttempt.Length; ++i) { cached = toRemoveAttempt[i].Value; if (cached != null) { creds = cached.Target; cached.Dispose(); if (!creds.IsClosed && !creds.IsInvalid && (cached = SafeCredentialReference.CreateReference(creds)) != null) { s_cachedCreds[toRemoveAttempt[i].Key] = cached; } else { s_cachedCreds.TryRemove(toRemoveAttempt[i].Key, out cached); } } } if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(null, $"Scavenged cache, New Cache Count = {s_cachedCreds.Count}"); } } } else if (NetEventSource.Log.IsEnabled()) { if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(null, $"CacheCredential() (locked retry) Found already cached Handle = {cached.Target}"); } } } } else if (NetEventSource.Log.IsEnabled()) { if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(null, $"CacheCredential() Ignoring incoming handle = {creds} since found already cached Handle = {cached.Target}"); } } }
private static unsafe void CompletionPortCallback(uint errorCode, uint numBytes, NativeOverlapped *nativeOverlapped) { Debug.Assert(OperatingSystem.IsWindows()); BaseOverlappedAsyncResult asyncResult = (BaseOverlappedAsyncResult)ThreadPoolBoundHandle.GetNativeOverlappedState(nativeOverlapped) !; if (asyncResult.InternalPeekCompleted) { NetEventSource.Fail(null, $"asyncResult.IsCompleted: {asyncResult}"); } if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(null, $"errorCode:{errorCode} numBytes:{numBytes} nativeOverlapped:{(IntPtr)nativeOverlapped}"); } // Complete the IO and invoke the user's callback. SocketError socketError = (SocketError)errorCode; if (socketError != SocketError.Success && socketError != SocketError.OperationAborted) { // There are cases where passed errorCode does not reflect the details of the underlined socket error. // "So as of today, the key is the difference between WSAECONNRESET and ConnectionAborted, // .e.g remote party or network causing the connection reset or something on the local host (e.g. closesocket // or receiving data after shutdown (SD_RECV)). With Winsock/TCP stack rewrite in longhorn, there may // be other differences as well." Socket?socket = asyncResult.AsyncObject as Socket; if (socket == null) { socketError = SocketError.NotSocket; } else if (socket.Disposed) { socketError = SocketError.OperationAborted; } else { try { // The async IO completed with a failure. // Here we need to call WSAGetOverlappedResult() just so GetLastSocketError() will return the correct error. SocketFlags ignore; bool success = Interop.Winsock.WSAGetOverlappedResult( socket.SafeHandle, nativeOverlapped, out numBytes, false, out ignore); if (!success) { socketError = SocketPal.GetLastSocketError(); } if (success) { NetEventSource.Fail(asyncResult, $"Unexpectedly succeeded. errorCode:{errorCode} numBytes:{numBytes}"); } } catch (ObjectDisposedException) { // Disposed check above does not always work since this code is subject to race conditions socketError = SocketError.OperationAborted; } } } // Set results and invoke callback asyncResult.CompletionCallback((int)numBytes, socketError); }