Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,20 @@ internal static X509Certificate2[] X509ChainGetCertificates(SafeX509ChainContext
Debug.Assert(res <= certPtrs.Length);

var certs = new X509Certificate2[certPtrs.Length];
for (int i = 0; i < res; i++)
try
{
certs[i] = new X509Certificate2(certPtrs[i]);
for (int i = 0; i < res; i++)
{
// X509Certificate2 duplicates these JNI global refs; the native-returned refs remain caller-owned.
certs[i] = new X509Certificate2(certPtrs[i]);
Comment thread
simonrozsival marked this conversation as resolved.
}
}
finally
{
for (int i = 0; i < res; i++)
{
Interop.JObjectLifetime.DeleteGlobalReference(certPtrs[i]);
}
}
Comment thread
simonrozsival marked this conversation as resolved.

if (res == certPtrs.Length)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates.Tests.Common;
using System.Text;
using System.Threading;
using Test.Cryptography;
Expand Down Expand Up @@ -376,6 +377,82 @@ public static void BuildChainCustomTrustStore(
}
}

[PlatformSpecific(TestPlatforms.Android)]
[OuterLoop("Builds 8,600 PKI chains to exceed Android's JNI global reference table; ~10 minutes on an Android emulator.")]
Comment thread
simonrozsival marked this conversation as resolved.
Outdated
[Fact]
public static void BuildChainRepeatedly_DoesNotExhaustGlobalReferences()
{
// Android aborts the process when its JNI global reference table overflows. This
// 6-certificate chain leaks 6 JNI global refs per successful build without the Android
// PAL cleanup, so 8,600 builds would leak 51,600 certificate refs. 8,400 iterations
// completed without the fix during threshold testing, while 8,500 iterations crashed
// with "global reference table overflow (max=51200)".
const int Iterations = 8_600;

CertificateAuthority.BuildPrivatePki(
PkiOptions.AllRevocation,
out RevocationResponder responder,
out CertificateAuthority root,
out CertificateAuthority[] intermediates,
out X509Certificate2 endCert,
intermediateAuthorityCount: 4,
registerAuthorities: false,
keyFactory: CertificateAuthority.KeyFactory.RSASize(2048));

using (responder)
using (root)
using (CertificateAuthority intermediate1 = intermediates[0])
using (CertificateAuthority intermediate2 = intermediates[1])
using (CertificateAuthority intermediate3 = intermediates[2])
using (CertificateAuthority intermediate4 = intermediates[3])
using (endCert)
using (ImportedCollection issuerHolder = new ImportedCollection(new X509Certificate2Collection
{
intermediate4.CloneIssuerCert(),
intermediate3.CloneIssuerCert(),
intermediate2.CloneIssuerCert(),
intermediate1.CloneIssuerCert(),
root.CloneIssuerCert(),
}))
using (ChainHolder chainHolder = new ChainHolder())
{
X509Certificate2Collection issuers = issuerHolder.Collection;
X509Chain chain = CreateChain(chainHolder, endCert, issuers);

// Each successful Android chain build materializes the chain from caller-owned JNI
// global references. Without releasing those native-returned references, this
// sequential public-API loop eventually exhausts Android process resources.
for (int i = 0; i < Iterations; i++)
{
if (!chain.Build(endCert))
{
Assert.Fail($"Chain build failed on iteration {i} with '{chain.AllStatusFlags()}'.");
}

if (i == 0)
{
Assert.Equal(issuers.Count + 1, chain.ChainElements.Count);
}

chainHolder.DisposeChainElements();
}
}

static X509Chain CreateChain(ChainHolder chainHolder, X509Certificate2 endCert, X509Certificate2Collection issuers)
{
X509Chain chain = chainHolder.Chain;

chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.ChainPolicy.VerificationTime = endCert.NotBefore.AddSeconds(1);
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
chain.ChainPolicy.DisableCertificateDownloads = true;
chain.ChainPolicy.ExtraStore.AddRange(issuers);
chain.ChainPolicy.CustomTrustStore.Add(issuers[issuers.Count - 1]);

return chain;
}
}

[Fact]
public static void BuildChainWithSystemTrustAndCustomTrustCertificates()
{
Expand Down
Loading