[Foundation] Don't leak exceptions in WrappedNSInputStream.Read. (#20131)

We don't want to leak exceptions back to the calling native code in WrappedNSInputStream.Read, because that will likely crash the process.

Example stack trace:

    ObjectDisposed_StreamClosed (System.ObjectDisposedException)
       at System.ThrowHelper.ThrowObjectDisposedException_StreamClosed(String) + 0x3c
       at System.IO.MemoryStream.Read(Byte[], Int32, Int32) + 0x124
       at System.Net.Http.MultipartContent.ContentReadStream.Read(Byte[], Int32, Int32) + 0x78
       at System.Net.Http.NSUrlSessionHandler.WrappedNSInputStream.Read(IntPtr buffer, UIntPtr len) + 0x58
       at MyApp!<BaseAddress>+0x7082f8

Instead return -1 from the Read method, which is documented as an error
condition, and then also return a custom NSError from the Error property -
which is also documented to be where the error is supposed to be surfaced.

Ref: https://developer.apple.com/documentation/foundation/nsinputstream/1411544-read

Ref: https://github.com/xamarin/xamarin-macios/issues/20123.
This commit is contained in:
Rolf Bjarne Kvinge 2024-02-21 10:29:33 +01:00 коммит произвёл GitHub
Родитель 16be59f7d6
Коммит 79ac366c8f
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
4 изменённых файлов: 64 добавлений и 11 удалений

Просмотреть файл

@ -0,0 +1,33 @@
using System;
using System.Runtime.Versioning;
#nullable enable
namespace Foundation {
#if NET
[SupportedOSPlatform ("ios")]
[SupportedOSPlatform ("maccatalyst")]
[SupportedOSPlatform ("macos")]
[SupportedOSPlatform ("tvos")]
#endif
public sealed class NSExceptionError : NSError {
Exception exception;
public Exception Exception { get => exception; }
public NSExceptionError (Exception exception)
: base ((NSString) exception.GetType ().FullName, exception.HResult, GetDictionary (exception))
{
this.exception = exception;
IsDirectBinding = false;
}
static NSDictionary GetDictionary (Exception e)
{
var dict = new NSMutableDictionary ();
dict [NSError.LocalizedDescriptionKey] = (NSString) e.Message;
dict [NSError.LocalizedFailureReasonErrorKey] = (NSString) e.Message;
return dict;
}
}
}

Просмотреть файл

@ -1428,6 +1428,7 @@ namespace Foundation {
CFRunLoopSource source;
readonly Stream stream;
bool notifying;
NSError? error;
public WrappedNSInputStream (Stream inputStream)
{
@ -1456,6 +1457,7 @@ namespace Foundation {
[Preserve (Conditional = true)]
public override nint Read (IntPtr buffer, nuint len)
{
try {
var sourceBytes = new byte [len];
var read = stream.Read (sourceBytes, 0, (int) len);
Marshal.Copy (sourceBytes, 0, buffer, (int) len);
@ -1471,6 +1473,18 @@ namespace Foundation {
notifying = false;
return read;
} catch (Exception e) {
// -1 means that the operation failed; more information about the error can be obtained with streamError.
error = new NSExceptionError (e);
return -1;
}
}
[Preserve (Conditional = true)]
public override NSError Error {
get {
return error ?? base.Error;
}
}
[Preserve (Conditional = true)]

Просмотреть файл

@ -826,6 +826,7 @@ FOUNDATION_SOURCES = \
Foundation/NSDistributedNotificationCenter.cs \
Foundation/NSEnumerator_1.cs \
Foundation/NSErrorException.cs \
Foundation/NSExceptionError.cs \
Foundation/NSExpression.cs \
Foundation/NSFastEnumerationState.cs \
Foundation/NSFastEnumerator.cs \

Просмотреть файл

@ -322,6 +322,11 @@ namespace Introspection {
if (!t.IsPublic || !NSObjectType.IsAssignableFrom (t))
continue;
// we only care about wrapper types (types with a native counterpart), and they all have a Register attribute.
var typeRegisterAttribute = t.GetCustomAttribute<RegisterAttribute> (false);
if (typeRegisterAttribute is null)
continue;
int designated = 0;
foreach (var ctor in t.GetConstructors ()) {
if (ctor.GetCustomAttribute<DesignatedInitializerAttribute> () is null)