Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Android NTLM: Empty Credentials now throws PlatformNotSupportedException #91131

Closed
dotMorten opened this issue Aug 25, 2023 · 32 comments · Fixed by #91160
Closed

Android NTLM: Empty Credentials now throws PlatformNotSupportedException #91131

dotMorten opened this issue Aug 25, 2023 · 32 comments · Fixed by #91160
Assignees
Milestone

Comments

@dotMorten
Copy link

dotMorten commented Aug 25, 2023

Description

If an empty network credential is used a recent change is now causing the exception System.PlatformNotSupportedException: 'NTLM authentication is not possible with default credentials on this platform.' to be thrown rather than treating the empty credential as null / not set.
This is a regression from .NET 7 behavior.

Reproduction Steps

On .NET Android, run the folllowing code:

string url = "https://url.to/my-iwa-secured-server";
var handler = new SocketsHttpHandler();
var cre = handler.Credentials;
handler.Credentials = CredentialCache.DefaultCredentials; // this is empty string username+password
//OR: handler.Credentials = new NetworkCredential();
HttpClient client = new HttpClient(handler);            
var result = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, url)); // Throws

Expected behavior

Returns 401 like .NET 7 would.

Actual behavior

[mono-rt] [ERROR] FATAL UNHANDLED EXCEPTION: System.PlatformNotSupportedException: NTLM authentication is not possible with default credentials on this platform.
[mono-rt]    at System.Net.NegotiateAuthenticationPal.ManagedNtlmNegotiateAuthenticationPal..ctor(NegotiateAuthenticationClientOptions clientOptions)
[mono-rt]    at System.Net.NegotiateAuthenticationPal.Create(NegotiateAuthenticationClientOptions clientOptions)
[mono-rt]    at System.Net.NegotiateAuthenticationPal.ManagedSpnegoNegotiateAuthenticationPal.CreateMechanismForPackage(String packageName)
[mono-rt]    at System.Net.NegotiateAuthenticationPal.ManagedSpnegoNegotiateAuthenticationPal.CreateSpNegoNegotiateMessage(ReadOnlySpan`1 incomingBlob, NegotiateAuthenticationStatusCode& statusCode)
[mono-rt]    at System.Net.NegotiateAuthenticationPal.ManagedSpnegoNegotiateAuthenticationPal.GetOutgoingBlob(ReadOnlySpan`1 incomingBlob, NegotiateAuthenticationStatusCode& statusCode)
[mono-rt]    at System.Net.Security.NegotiateAuthentication.GetOutgoingBlob(ReadOnlySpan`1 incomingBlob, NegotiateAuthenticationStatusCode& statusCode)
[mono-rt]    at System.Net.Security.NegotiateAuthentication.GetOutgoingBlob(String incomingBlob, NegotiateAuthenticationStatusCode& statusCode)
[mono-rt]    at System.Net.Http.AuthenticationHelper.SendWithNtAuthAsync(HttpRequestMessage request, Uri authUri, Boolean async, ICredentials credentials, Boolean isProxyAuth, HttpConnection connection, HttpConnectionPool connectionPool, CancellationToken cancellationToken)
[mono-rt]    at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
[mono-rt]    at System.Net.Http.AuthenticationHelper.SendWithAuthAsync(HttpRequestMessage request, Uri authUri, Boolean async, ICredentials credentials, Boolean preAuthenticate, Boolean isProxyAuth, Boolean doRequestAuth, HttpConnectionPool pool, CancellationToken cancellationToken)
[mono-rt]    at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
[mono-rt]    at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
[mono-rt]    at IWATest.MainPage.OnCounterClicked(Object sender, EventArgs e) in E:\sources.tmp\IWATest\IWATest\MainPage.xaml.cs:line 25
[mono-rt]    at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__128_0(Object state)
[mono-rt]    at Android.App.SyncContext.<>c__DisplayClass2_0.<Post>b__0() in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Android.App/SyncContext.cs:line 36
[mono-rt]    at Java.Lang.Thread.RunnableImplementor.Run() in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Java.Lang/Thread.cs:line 36
[mono-rt]    at Java.Lang.IRunnableInvoker.n_Run(IntPtr jnienv, IntPtr native__this) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/obj/Release/net8.0/android-34/mcw/Java.Lang.IRunnable.cs:line 84
[mono-rt]    at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(_JniMarshal_PP_V callback, IntPtr jnienv, IntPtr klazz) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs:line 22

Regression?

Yes

Known Workarounds

Check whether the credential is empty before setting, but has effect on already released libraries or cross-platform code.

Configuration

.NET 8 Preview 7 (Android)

Other information

/CC @wfurt @filipnavara as discussed

@ghost ghost added the untriaged New issue has not been triaged by the area owner label Aug 25, 2023
@ghost
Copy link

ghost commented Aug 25, 2023

Tagging subscribers to this area: @dotnet/ncl, @bartonjs, @vcsjones
See info in area-owners.md if you want to be subscribed.

Issue Details

Description

If an empty network credential is used a recent change is now causing the exception System.PlatformNotSupportedException: 'NTLM authentication is not possible with default credentials on this platform.' to be thrown rather than treating the empty credential as null / not set.
This is a regression from .NET 7 behavior.

Reproduction Steps

On .NET Android, run the folllowing code:

string url = "https://url.to/my-iwa-secured-server";
var handler = new SocketsHttpHandler();
var cre = handler.Credentials;
handler.Credentials = CredentialCache.DefaultCredentials; // this is empty string username+password
//OR: handler.Credentials = new NetworkCredential();
HttpClient client = new HttpClient(handler);            
var result = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, url)); // Throws

Expected behavior

Returns 401 like .NET 7 would.

Actual behavior

[mono-rt] [ERROR] FATAL UNHANDLED EXCEPTION: System.PlatformNotSupportedException: NTLM authentication is not possible with default credentials on this platform.
[mono-rt]    at System.Net.NegotiateAuthenticationPal.ManagedNtlmNegotiateAuthenticationPal..ctor(NegotiateAuthenticationClientOptions clientOptions)
[mono-rt]    at System.Net.NegotiateAuthenticationPal.Create(NegotiateAuthenticationClientOptions clientOptions)
[mono-rt]    at System.Net.NegotiateAuthenticationPal.ManagedSpnegoNegotiateAuthenticationPal.CreateMechanismForPackage(String packageName)
[mono-rt]    at System.Net.NegotiateAuthenticationPal.ManagedSpnegoNegotiateAuthenticationPal.CreateSpNegoNegotiateMessage(ReadOnlySpan`1 incomingBlob, NegotiateAuthenticationStatusCode& statusCode)
[mono-rt]    at System.Net.NegotiateAuthenticationPal.ManagedSpnegoNegotiateAuthenticationPal.GetOutgoingBlob(ReadOnlySpan`1 incomingBlob, NegotiateAuthenticationStatusCode& statusCode)
[mono-rt]    at System.Net.Security.NegotiateAuthentication.GetOutgoingBlob(ReadOnlySpan`1 incomingBlob, NegotiateAuthenticationStatusCode& statusCode)
[mono-rt]    at System.Net.Security.NegotiateAuthentication.GetOutgoingBlob(String incomingBlob, NegotiateAuthenticationStatusCode& statusCode)
[mono-rt]    at System.Net.Http.AuthenticationHelper.SendWithNtAuthAsync(HttpRequestMessage request, Uri authUri, Boolean async, ICredentials credentials, Boolean isProxyAuth, HttpConnection connection, HttpConnectionPool connectionPool, CancellationToken cancellationToken)
[mono-rt]    at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
[mono-rt]    at System.Net.Http.AuthenticationHelper.SendWithAuthAsync(HttpRequestMessage request, Uri authUri, Boolean async, ICredentials credentials, Boolean preAuthenticate, Boolean isProxyAuth, Boolean doRequestAuth, HttpConnectionPool pool, CancellationToken cancellationToken)
[mono-rt]    at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
[mono-rt]    at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
[mono-rt]    at IWATest.MainPage.OnCounterClicked(Object sender, EventArgs e) in E:\sources.tmp\IWATest\IWATest\MainPage.xaml.cs:line 25
[mono-rt]    at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__128_0(Object state)
[mono-rt]    at Android.App.SyncContext.<>c__DisplayClass2_0.<Post>b__0() in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Android.App/SyncContext.cs:line 36
[mono-rt]    at Java.Lang.Thread.RunnableImplementor.Run() in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Java.Lang/Thread.cs:line 36
[mono-rt]    at Java.Lang.IRunnableInvoker.n_Run(IntPtr jnienv, IntPtr native__this) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/obj/Release/net8.0/android-34/mcw/Java.Lang.IRunnable.cs:line 84
[mono-rt]    at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(_JniMarshal_PP_V callback, IntPtr jnienv, IntPtr klazz) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs:line 22

Regression?

Yes

Known Workarounds

Check whether the credential is empty before setting, but has effect on already released libraries.

Configuration

.NET 8 Preview 7 (Android)

Other information

/CC @wfurt @filipnavara as discussed

Author: dotMorten
Assignees: -
Labels:

area-System.Net.Security

Milestone: -

@filipnavara
Copy link
Member

filipnavara commented Aug 25, 2023

Check whether the credential is empty before setting

You cannot check that. The value is a placeholder that's always empty.


The question is what is the expected behavior when you specify CredentialCache.DefaultCredentials, the server requests NTLM/Negotiate, and the platform doesn't support it.

.NET 6 on Linux/macOS could throw PlatformNotSupportedException (1).
.NET 7 will likely return you 401 Unauthorized and not throw, on Linux/macOS/Android (1 2).
.NET 8 is back to throwing PlatformNotSupportedException.

PlatformNotSupportedException explicitly tells you what you did wrong, what is not supported, and you can avoid it as a developer.
Returning 401 Unauthorized is slightly more consistent with other error scenarios, but completely hides the underlying issue from the developer and makes it incredibly difficult to debug.

On API level, the .NET 8 change is that NegotiateAuthentication API throws the PlatformNotSupportedException exception from constructor instead of returning Unsupported from first GetOutgoingBlob call. In that case it provides the API consumer with more actionable information. (On second thought, we do communicate unsupported protocols through the Unsupported status code instead of an exception 🤷 )

We can change HttpClient to handle PlatformNotSupportedException and pass through the 401 Unauthorized response.

@karelz @wfurt Thoughts?

@dotMorten
Copy link
Author

My suggestion would be to special-case empty credentials and treat them as if they weren't set. They are in way the same thing.

@filipnavara
Copy link
Member

My suggestion would be to special-case empty credentials and treat them as if they weren't set. They are in way the same thing.

CredentialCache.DefaultCredentials is always empty, on all platforms, even the ones where SSO works. It simply acts as a placeholder value.

By that definition NegotiateAuthentication API already special cases it. On Windows it translates to telling SSPI to use default credentials, on Linux/macOS it translates to GSSAPI acquiring default credentials (or throwing PNSE for NTLM-only case), and on Android/tvOS it throws PNSE unconditionally.

What I am proposing is to swallow the PNSE in SocketsHttpHandler and pass on the original HTTP response to the caller.

@SteveSyfuhs
Copy link

If you're going out of your way to specify what credentials are used, and those credentials aren't supported, it should throw. The platform in question doesn't support default credentials. Having it translate to an application-level error is just weird.

handler.Credentials = CredentialCache.DefaultCredentials; // this is empty string username+password

Additionally, since it's NTLM, I actually think it should always throw PNSE for DefaultCredentials as a matter of security hygiene, and should only support supplied credentials, that way it's signaling to the developer how bad this is.

@dotMorten
Copy link
Author

If you're going out of your way to specify what credentials are used,

On the contrary I'm going out of my way not to write platform specific code. If the platform doesn't support default credentials, why does CredentialCache.DefaultCredentials not return null then?

@filipnavara
Copy link
Member

why does CredentialCache.DefaultCredentials not return null then?

Firstly, it was never defined that way. It cannot be changed to return null now since it's non-nullable.
Secondly, the system may have default credentials for Kerberos but not for NTLM. There's simply not a way to convey that information in ICredentials/NetworkCredential, and often getting that information can be quite expensive.

@dotMorten
Copy link
Author

If I have invalid credentials a 401 seems like the right response, which is what .net7 did

@filipnavara
Copy link
Member

If I have invalid credentials a 401 seems like the right response, which is what .net7 did

I see where you are coming from, and I tend to agree to a point. The problem with HttpClient is that it doesn't tell you outright that it is not supported. In fact, it doesn't even tell you if you make some HTTP request. It literally only tells you when the server requests the Negotiate/NTLM authentication, and that's not a good experience. That's why I feel a special handling there may be in order. If you specify CredentialCache.DefaultCredentials and the server offers you Basic authentication I would expect this to result in "incompatible match" and the 401 Unauthorized returned to the HttpClient.SendAsync caller.

That said, I believe the current behavior of NegotiateAuthentication itself is the correct one. You pass a parameter that is invalid for a given platform, and get an exception right away.

@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Aug 25, 2023
@karelz
Copy link
Member

karelz commented Aug 26, 2023

@wfurt I am curious what you think. Being in line with .NET 7 behavior makes sense to me.

@filipnavara
Copy link
Member

Being in line with .NET 7 behavior makes sense to me.

I agree when it comes to the observable behavior on HttpClient/SocketsHttpHandler.

I'm not sure I agree when it comes to the observable behavior on NegotiateAuthentication. For example,

var na = new NegotiateAuthentication(new NegotiateAuthenticationClientOptions { Package = "NTLM" });
na.GetOutgoingBlob("", out var status);

This will always fail on any non-Windows platform. Would you expect this to throw PlatformNotSupportedException("NTLM authentication is not possible with default credentials on this platform."), or would you expect it to have no exception and status == NegotiateAuthenticationStatusCode.Unsupported? What if this is wrapped inside SQL Client logic and you just try to connect to database (PNSE vs some generic auth exception)?

@dotMorten
Copy link
Author

Not sure I get the sql client argument. If I talk to a webserver that I can't authenticate with, I expect a 401 response code. Having to deal with certain platforms now throwing in certain cases for specific credentials makes no sense to me and makes it really hard to write libraries that run on multiple platforms.

@filipnavara
Copy link
Member

filipnavara commented Aug 26, 2023

Not sure I get the sql client argument.

It's an argument about non-HTTP scenario. NegotiateAuthentication is a building block that's used by HTTP, SMTP, SQL Server, and NegotiateStream, among others.

In case of NegotiateStream or something like SQL Server, there's a direct correlation between calling the API with some parameters and synchronously getting the PlatformNotSupportedException.

If I talk to a webserver that I can't authenticate with, I expect a 401 response code. Having to deal with certain platforms now throwing in certain cases for specific credentials makes no sense to me and makes it really hard to write libraries that run on multiple platforms.

With <= .NET 6 you could actually get Win32Exception in the authentication scenarios, and I agree that's pretty unreasonable. That's why I fixed it in .NET 7 to avoid that.

I would argue that anyone using CredentialCache.DefaultCredentials on mobile platform may incorrectly be lead to believe that it can work at all. That's not the case on Android, tvOS, and in most cases on iOS. The standard API way to say that something is not supported on a given platform is the PlatformNotSupportedException. That said, in this particular case I find it problematic because it doesn't happen at the time you call handler.Credentials = CredentialCache.DefaultCredentials but it happens at some later point and it depends on the server requesting the Negotiate/NTLM authentication. Thus throwing PlatformNotSupportedException from HttpClient violates the principle of least surprise.

TL;DR: I am arguing for new NegotiateAuthentication(...) to throw PlatformNotSupportedException, and for HttpClient.Send[Async] NOT to throw PlatformNotSupportedException.

@filipnavara
Copy link
Member

filipnavara commented Aug 26, 2023

The alternative to PNSE in context of NegotiateAuthentication is to map to the closest NegotiateAuthenticationStatus, which in this case would likely be UnknownCredentials. I think that's what you get on Windows that is not domain-joined (to be verified).

@SteveSyfuhs
Copy link

SteveSyfuhs commented Aug 26, 2023 via email

@filipnavara
Copy link
Member

filipnavara commented Aug 26, 2023

UnknownCredentials is probably the equivalent?

Correct. SEC_E_NO_CREDENTIALS would translate to NegotiateAuthenticationStatus.UnknownCredentials.

But also now you're mixing error behaviors between status codes and exceptions.

The question is whether it makes sense to "emulate Windows" here, or throw an exception outright.

In case of the low-level NegotiateAuthentication API this boils down to:

  1. Throwing PNSE:
    [+] Accurate message for diagnostics
    [-] May need extra handling on the API caller side
    [ ] That's what the .NET 6 private API surface did
    [ ] Diff: https://github.com/dotnet/runtime/compare/main...filipnavara:runtime:nego-no-pnse?expand=1 (Make handling of DefaultCredentials in NegotiateAuthentication/SocketsHttpHandler more consistent #91138)

  2. Communicating back with the status code:
    [+] Closer to what happens on Windows, so easier API to write against
    [-] Bad diagnostics (ie. optional tracing has to be turned on to find out the real error)
    [ ] That's closer to what .NET 7 public API shipped with, although unintentionally
    [ ] Diff: https://github.com/dotnet/runtime/compare/main...filipnavara:runtime:nego-no-pnse-alt?expand=1 (Make handling of DefaultCredentials in NegotiateAuthentication/SocketsHttpHandler more consistent #91160)

@dotMorten
Copy link
Author

dotMorten commented Aug 26, 2023

I look at it from a developer point of view. Ie, to me it seems weird that I have to deal with things different:

handler.Credential = null; // returns 401
handler.Credential = new NetworkCredential("invalid user", "invalid password"); // returns 401
handler.Credential = new NetworkCredential("", ""); // Throws platform not supported on some platforms because user accepted sign-in dialog before entering anything
handler.Credential = CredentialCache.DefaultCredentials; // Throws platform not supported

In all of these cases, I need to do the exact same thing: I need to prompt the user for a valid set of credentials. Just like I do for any other 401 response. But the way I do it is currently in .NET 8 different on different platforms.

@wfurt
Copy link
Member

wfurt commented Aug 29, 2023

Reading the thread I would probably be inclined to support the idea that the low-level API returns specific error e.g. NegotiateAuthenticationStatus.UnknownCredentials. It would be up to the callers to deal with that. That can for example trow for NegotateStream to make it clear and it may turn it into "silent" 401 failures for HttpClient. While I understand some of the arguments @SteveSyfuhs, the backward compatibility is quite important for us e.g. just change the behavior because we feel it is better is problematic.

I'm not big fan of throwing/catching/ignoring. I can live with 401 for cases when the password happens to be empty. The explicit use of CredentialCache.DefaultCredentials seems more problematic as this is never going to work in certain scenarios. The trick here is that for Kerberos it may work but not for NTLM -> something the developer may not control of as the particular mechanisms are selected during authentication.

From that prospective I'm probably in favor of #91160

@filipnavara
Copy link
Member

After writing both versions of the code I am inclined to agree that returning status codes is less disrupting for the current API consumers. That said, we should probably have a documentation on how to enable the detailed tracing. Right now the steps are just scattered around in several issues and random snippets at gist.github.com, which is far from ideal.

@ghost ghost removed in-pr There is an active PR which will close this issue when it is merged untriaged New issue has not been triaged by the area owner labels Aug 30, 2023
@wfurt
Copy link
Member

wfurt commented Aug 30, 2023

I think we should take this one as well for 8.0 @karelz as it can be viewed as 8.0 regression.

@karelz
Copy link
Member

karelz commented Sep 6, 2023

Reopening to track the backport.

@karelz karelz reopened this Sep 6, 2023
@karelz karelz added this to the 8.0.0 milestone Sep 6, 2023
@ghost ghost added the untriaged New issue has not been triaged by the area owner label Sep 6, 2023
@karelz karelz added the bug label Sep 6, 2023
@dotMorten
Copy link
Author

.NET 6 on Linux/macOS could throw PlatformNotSupportedException (1).
.NET 7 will likely return you 401 Unauthorized and not throw, on Linux/macOS/Android (1 2).
.NET 8 is back to throwing PlatformNotSupportedException.

Just wanted to follow-up on this bit. I tried on a domain-joined MacOS with net7.0-maccatalyst, and it works just fine there. The default credential correctly authenticates with the server using the provided repo.

@karelz
Copy link
Member

karelz commented Sep 7, 2023

@dotMorten would you be able to validate daily 9.0 build with the fix above?
That will help us to justify the backport to 8.0.

I talked with @filipnavara and we agreed to create a targeted fix for 8.0 - just try-catch around all managed NTLM invocations.
It will scope the changes only to the affected platforms.
Once we have a fix, it would be great to validate it on private bits from 8.0 branch. @dotMorten will you be able to help us out with that? (we would provide the bits / instructions)

cc @rzikm - I will need your help with the private bits here

@dotMorten
Copy link
Author

Sure. Bring it!

@karelz karelz removed the untriaged New issue has not been triaged by the area owner label Sep 7, 2023
@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Sep 7, 2023
@rzikm
Copy link
Member

rzikm commented Sep 11, 2023

@dotMorten There it is, it's from a local Debug build since we don't publish Android artifacts in the pipelines. Should work with both x64 and arm architectures as managed dlls are not arch specific (only OS-specific).
System.Net.Security.dll.zip

I am not familiar with the Android publishing process, but for regular desktop bits, the process is

  • Build repro as self-contained
  • Replace relevant binaries in the publish folder

An alternative is to modify your installed .net sdk bits (overwrite dlls in $DOTNET_ROOT/shared/Microsoft.NetCore.App/<version>), with obvious implications.

@karelz karelz self-assigned this Sep 11, 2023
@karelz
Copy link
Member

karelz commented Sep 12, 2023

@dotMorten will you have time to test it out on your side please? Thanks!

@dotMorten
Copy link
Author

@rzikm is $DOTNET_ROOT/shared/Microsoft.NetCore.App/<version> correct? The new file is about 3 times smaller than the one already there, and I see this DLL spread out all over the Android workloads:

image

Lastly I don't see this DLL (or any of the other DLLs) in the generated Android package.

@rzikm
Copy link
Member

rzikm commented Sep 12, 2023

Uh... I am not that familiar with how Android packages get build. But my intuition tells me to try replace the one which has "android" and .net 8 preview version in path (maybe all of them)

As for size, my guess is that published DLLs are distributed "ready to run", i.e. with pre-jitted machine code, which is not being done for dev builds. But I may be wrong.

@simonrozsival is what we are attempting the right way of testing private bits on Android? If not, can you please help?

@steveisok
Copy link
Member

@rzikm @dotMorten In order to validate, you should be able to replace the assemblies in the android runtime packs that are installed via dotnet workload install. @dotMorten, from your image, that is all of the runtime packs (or the versions you want to replace) under C:\Program Files\dotnet\packs. After that, clean and rebuild and you should be using the update assembly.

I don't think you'll see the actual assemblies in your apk because the android sdk team has a crafty resource bundling scheme.

@steveisok
Copy link
Member

@rzikm unlike desktop, by default mobile configurations produce self contained apps. They will not use the shared framework.

@dotMorten
Copy link
Author

Got it! I guess I didn't clean up enough the first time around. I can confirm that the fix works!

@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Sep 14, 2023
@karelz
Copy link
Member

karelz commented Sep 14, 2023

Fixed in main (9.0) in PR #91160 and 8.0 (RC2) in PR #91753

@karelz karelz closed this as completed Sep 14, 2023
@ghost ghost locked as resolved and limited conversation to collaborators Oct 15, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.