Skip to content

Commit a17b872

Browse files
authored
Build against Y2038-compatible glibc for linux arm32 (#102410)
This updates our linux arm32 build to build against a more recent glibc that supports _TIME_BITS (which we set to 64). Since openssl may be using either 32-bit or 64-bit time_t, this includes detection logic to determine which case we are in, and avoid passing time values that don't fit in 32 bits to openssl. The arm build image is updated to the latest version of the images added in dotnet/dotnet-buildtools-prereqs-docker#1037. The helix test images are updated to debian images added in dotnet/dotnet-buildtools-prereqs-docker#1041. Additional context: Additional context: Reintroduces the fix for Y2038 support on arm32 linux (#102059), which was reverted due to problems running against openssl built with _TIME_BITS=32. Fixes #101444 (both the originally reported issue, and the test failures mentioned in #101444 (comment)). Supports: #91826
1 parent 258ae79 commit a17b872

File tree

7 files changed

+91
-30
lines changed

7 files changed

+91
-30
lines changed

eng/pipelines/common/templates/pipeline-with-resources.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ extends:
1717

1818
containers:
1919
linux_arm:
20-
image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-cross-arm-net9.0-20240507035943-1390eea
20+
image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-cross-arm-net9.0
2121
env:
2222
ROOTFS_DIR: /crossrootfs/arm
2323

eng/pipelines/coreclr/templates/helix-queues-setup.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@ jobs:
6363
# Linux arm
6464
- ${{ if eq(parameters.platform, 'linux_arm') }}:
6565
- ${{ if eq(variables['System.TeamProject'], 'public') }}:
66-
- (Ubuntu.1804.Arm32.Open)Ubuntu.2004.Armarch[email protected]/dotnet-buildtools/prereqs:ubuntu-18.04-helix-arm32v7
66+
- (Debian.12.Arm32.Open)Ubuntu.2004.ArmArch[email protected]/dotnet-buildtools/prereqs:debian-12-helix-arm32v7
6767
- ${{ if eq(variables['System.TeamProject'], 'internal') }}:
68-
- (Ubuntu.1804.Arm32)Ubuntu.2004.Armarch@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-18.04-helix-arm32v7
68+
- (Debian.12.Arm32)Ubuntu.2004.ArmArch@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-12-helix-arm32v7
6969

7070
# Linux arm64
7171
- ${{ if eq(parameters.platform, 'linux_arm64') }}:

eng/pipelines/libraries/helix-queues-setup.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
# Linux arm
2727
- ${{ if eq(parameters.platform, 'linux_arm') }}:
2828
- ${{ if or(eq(parameters.jobParameters.isExtraPlatformsBuild, true), eq(parameters.jobParameters.includeAllPlatforms, true)) }}:
29-
- (Debian.11.Arm32.Open)[email protected]/dotnet-buildtools/prereqs:debian-11-helix-arm32v7
29+
- (Debian.12.Arm32.Open)[email protected]/dotnet-buildtools/prereqs:debian-12-helix-arm32v7
3030

3131
# Linux armv6
3232
- ${{ if eq(parameters.platform, 'linux_armv6') }}:

src/native/libs/System.Security.Cryptography.Native/openssl.c

+14
Original file line numberDiff line numberDiff line change
@@ -964,6 +964,20 @@ int32_t CryptoNative_X509StoreSetVerifyTime(X509_STORE* ctx,
964964
return 0;
965965
}
966966

967+
#if defined(FEATURE_DISTRO_AGNOSTIC_SSL) && defined(TARGET_ARM) && defined(TARGET_LINUX)
968+
if (g_libSslUses32BitTime)
969+
{
970+
if (verifyTime > INT_MAX || verifyTime < INT_MIN)
971+
{
972+
return 0;
973+
}
974+
975+
// Cast to a signature that takes a 32-bit value for the time.
976+
((void (*)(X509_VERIFY_PARAM*, int32_t))(void*)(X509_VERIFY_PARAM_set_time))(verifyParams, (int32_t)verifyTime);
977+
return 1;
978+
}
979+
#endif
980+
967981
X509_VERIFY_PARAM_set_time(verifyParams, verifyTime);
968982
return 1;
969983
}

src/native/libs/System.Security.Cryptography.Native/opensslshim.c

+35
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ FOR_ALL_OPENSSL_FUNCTIONS
2525
#undef LIGHTUP_FUNCTION
2626
#undef REQUIRED_FUNCTION_110
2727
#undef REQUIRED_FUNCTION
28+
#if defined(TARGET_ARM) && defined(TARGET_LINUX)
29+
TYPEOF(OPENSSL_gmtime) OPENSSL_gmtime_ptr;
30+
#endif
2831

2932
// x.x.x, considering the max number of decimal digits for each component
3033
#define MaxVersionStringLength 32
@@ -41,6 +44,15 @@ FOR_ALL_OPENSSL_FUNCTIONS
4144
#define MAKELIB(v) SONAME_BASE v
4245
#endif
4346

47+
#if defined(TARGET_ARM) && defined(TARGET_LINUX)
48+
// We support ARM32 linux distros that have Y2038-compatible glibc (those which support _TIME_BITS).
49+
// Some such distros have not yet switched to _TIME_BITS=64 by default, so we may be running against an openssl
50+
// that expects 32-bit time_t even though our time_t is 64-bit.
51+
// This can be deleted once the minimum supported Linux Arm32 distros are
52+
// at least Debian 13 and Ubuntu 24.04.
53+
bool g_libSslUses32BitTime = false;
54+
#endif
55+
4456
static void DlOpen(const char* libraryName)
4557
{
4658
void* libsslNew = dlopen(libraryName, RTLD_LAZY);
@@ -205,6 +217,10 @@ void InitializeOpenSSLShim(void)
205217
#undef LIGHTUP_FUNCTION
206218
#undef REQUIRED_FUNCTION_110
207219
#undef REQUIRED_FUNCTION
220+
#if defined(TARGET_ARM) && defined(TARGET_LINUX)
221+
if (!(OPENSSL_gmtime_ptr = (TYPEOF(OPENSSL_gmtime))(dlsym(libssl, "OPENSSL_gmtime")))) { fprintf(stderr, "Cannot get required symbol OPENSSL_gmtime from libssl\n"); abort(); }
222+
#endif
223+
208224

209225
// Sanity check that we have at least one functioning way of reporting errors.
210226
if (ERR_put_error_ptr == &local_ERR_put_error)
@@ -215,4 +231,23 @@ void InitializeOpenSSLShim(void)
215231
abort();
216232
}
217233
}
234+
235+
#if defined(TARGET_ARM) && defined(TARGET_LINUX)
236+
// This value will represent a time in year 2038 if 64-bit time is used,
237+
// or 1901 if the lower 32 bits are interpreted as a 32-bit time_t value.
238+
time_t timeVal = (time_t)INT_MAX + 1;
239+
struct tm tmVal = { 0 };
240+
241+
// Detect whether openssl is using 32-bit or 64-bit time_t.
242+
// If it uses 32-bit time_t, little-endianness means that the pointer
243+
// will be interpreted as a pointer to the lower 32 bits of timeVal.
244+
// tm_year is the number of years since 1900.
245+
if (!OPENSSL_gmtime(&timeVal, &tmVal) || (tmVal.tm_year != 138 && tmVal.tm_year != 1))
246+
{
247+
fprintf(stderr, "Cannot determine the time_t size used by libssl\n");
248+
abort();
249+
}
250+
251+
g_libSslUses32BitTime = (tmVal.tm_year == 1);
252+
#endif
218253
}

src/native/libs/System.Security.Cryptography.Native/opensslshim.h

+8-3
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,10 @@ int EVP_DigestSqueeze(EVP_MD_CTX *ctx, unsigned char *out, size_t outlen);
187187

188188
#define API_EXISTS(fn) (fn != NULL)
189189

190+
#if defined(FEATURE_DISTRO_AGNOSTIC_SSL) && defined(TARGET_ARM) && defined(TARGET_LINUX)
191+
extern bool g_libSslUses32BitTime;
192+
#endif
193+
190194
// List of all functions from the libssl that are used in the System.Security.Cryptography.Native.
191195
// Forgetting to add a function here results in build failure with message reporting the function
192196
// that needs to be added.
@@ -618,7 +622,6 @@ int EVP_DigestSqueeze(EVP_MD_CTX *ctx, unsigned char *out, size_t outlen);
618622
REQUIRED_FUNCTION(SSL_version) \
619623
FALLBACK_FUNCTION(X509_check_host) \
620624
REQUIRED_FUNCTION(X509_check_purpose) \
621-
REQUIRED_FUNCTION(X509_cmp_current_time) \
622625
REQUIRED_FUNCTION(X509_cmp_time) \
623626
REQUIRED_FUNCTION(X509_CRL_free) \
624627
FALLBACK_FUNCTION(X509_CRL_get0_nextUpdate) \
@@ -717,7 +720,9 @@ FOR_ALL_OPENSSL_FUNCTIONS
717720
#undef LIGHTUP_FUNCTION
718721
#undef REQUIRED_FUNCTION_110
719722
#undef REQUIRED_FUNCTION
720-
723+
#if defined(TARGET_ARM) && defined(TARGET_LINUX)
724+
extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr;
725+
#endif
721726
// Redefine all calls to OpenSSL functions as calls through pointers that are set
722727
// to the functions from the libssl.so selected by the shim.
723728
#define a2d_ASN1_OBJECT a2d_ASN1_OBJECT_ptr
@@ -1018,6 +1023,7 @@ FOR_ALL_OPENSSL_FUNCTIONS
10181023
#define OCSP_RESPONSE_new OCSP_RESPONSE_new_ptr
10191024
#define OPENSSL_add_all_algorithms_conf OPENSSL_add_all_algorithms_conf_ptr
10201025
#define OPENSSL_cleanse OPENSSL_cleanse_ptr
1026+
#define OPENSSL_gmtime OPENSSL_gmtime_ptr
10211027
#define OPENSSL_init_ssl OPENSSL_init_ssl_ptr
10221028
#define OPENSSL_sk_free OPENSSL_sk_free_ptr
10231029
#define OPENSSL_sk_new_null OPENSSL_sk_new_null_ptr
@@ -1149,7 +1155,6 @@ FOR_ALL_OPENSSL_FUNCTIONS
11491155
#define TLS_method TLS_method_ptr
11501156
#define X509_check_host X509_check_host_ptr
11511157
#define X509_check_purpose X509_check_purpose_ptr
1152-
#define X509_cmp_current_time X509_cmp_current_time_ptr
11531158
#define X509_cmp_time X509_cmp_time_ptr
11541159
#define X509_CRL_free X509_CRL_free_ptr
11551160
#define X509_CRL_get0_nextUpdate X509_CRL_get0_nextUpdate_ptr

src/native/libs/System.Security.Cryptography.Native/pal_x509.c

+30-23
Original file line numberDiff line numberDiff line change
@@ -893,14 +893,12 @@ static OCSP_CERTID* MakeCertId(X509* subject, X509* issuer)
893893
return OCSP_cert_to_id(EVP_sha1(), subject, issuer);
894894
}
895895

896-
static time_t GetIssuanceWindowStart(void)
896+
static time_t GetIssuanceWindowStart(time_t currentTime)
897897
{
898898
// time_t granularity is seconds, so subtract 4 days worth of seconds.
899899
// The 4 day policy is based on the CA/Browser Forum Baseline Requirements
900900
// (version 1.6.3) section 4.9.10 (On-Line Revocation Checking Requirements)
901-
time_t t = time(NULL);
902-
t -= 4 * 24 * 60 * 60;
903-
return t;
901+
return currentTime - 4 * 24 * 60 * 60;
904902
}
905903

906904
static X509VerifyStatusCode CheckOcspGetExpiry(OCSP_REQUEST* req,
@@ -960,28 +958,37 @@ static X509VerifyStatusCode CheckOcspGetExpiry(OCSP_REQUEST* req,
960958

961959
if (OCSP_resp_find_status(basicResp, certId, &status, NULL, NULL, &thisupd, &nextupd))
962960
{
963-
// X509_cmp_current_time uses 0 for error already, so we can use it when there's a null value.
964-
// 1 means the nextupd value is in the future, -1 means it is now-or-in-the-past.
965-
// Following with OpenSSL conventions, we'll accept "now" as "the past".
966-
int nextUpdComparison = nextupd == NULL ? 0 : X509_cmp_current_time(nextupd);
967-
968-
// Un-revoking is rare, so reporting revoked on an expired response has a low chance
969-
// of a false-positive.
970-
//
971-
// For non-revoked responses, a next-update value in the past counts as expired.
972-
if (status == V_OCSP_CERTSTATUS_REVOKED)
973-
{
974-
ret = PAL_X509_V_ERR_CERT_REVOKED;
975-
}
976-
else
961+
time_t currentTime = time(NULL);
962+
int nextUpdComparison = 0;
963+
#if defined(FEATURE_DISTRO_AGNOSTIC_SSL) && defined(TARGET_ARM) && defined(TARGET_LINUX)
964+
// If openssl uses 32-bit time_t and the current time doesn't fit in 32 bits,
965+
// skip checking the status/nextupd, and fall through to return PAL_X509_V_ERR_UNABLE_TO_GET_CRL.
966+
if (!g_libSslUses32BitTime || (currentTime >= INT_MIN && currentTime <= INT_MAX))
967+
#endif
977968
{
978-
if (nextupd != NULL && nextUpdComparison <= 0)
969+
// X509_cmp_current_time uses 0 for error already, so we can use it when there's a null value.
970+
// 1 means the nextupd value is in the future, -1 means it is now-or-in-the-past.
971+
// Following with OpenSSL conventions, we'll accept "now" as "the past".
972+
nextUpdComparison = nextupd == NULL ? 0 : X509_cmp_time(nextupd, &currentTime);
973+
974+
// Un-revoking is rare, so reporting revoked on an expired response has a low chance
975+
// of a false-positive.
976+
//
977+
// For non-revoked responses, a next-update value in the past counts as expired.
978+
if (status == V_OCSP_CERTSTATUS_REVOKED)
979979
{
980-
ret = PAL_X509_V_ERR_CRL_HAS_EXPIRED;
980+
ret = PAL_X509_V_ERR_CERT_REVOKED;
981981
}
982-
else if (status == V_OCSP_CERTSTATUS_GOOD)
982+
else
983983
{
984-
ret = PAL_X509_V_OK;
984+
if (nextupd != NULL && nextUpdComparison <= 0)
985+
{
986+
ret = PAL_X509_V_ERR_CRL_HAS_EXPIRED;
987+
}
988+
else if (status == V_OCSP_CERTSTATUS_GOOD)
989+
{
990+
ret = PAL_X509_V_OK;
991+
}
985992
}
986993
}
987994

@@ -997,7 +1004,7 @@ static X509VerifyStatusCode CheckOcspGetExpiry(OCSP_REQUEST* req,
9971004
thisupd != NULL &&
9981005
nextUpdComparison > 0)
9991006
{
1000-
time_t oldest = GetIssuanceWindowStart();
1007+
time_t oldest = GetIssuanceWindowStart(currentTime);
10011008

10021009
if (X509_cmp_time(thisupd, &oldest) > 0)
10031010
{

0 commit comments

Comments
 (0)