Skip to content

Commit

Permalink
Build against Y2038-compatible glibc for linux arm32 (dotnet#102410)
Browse files Browse the repository at this point in the history
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 (dotnet#102059), which was
reverted due to problems running against openssl built with
_TIME_BITS=32.

Fixes dotnet#101444 (both the
originally reported issue, and the test failures mentioned in
dotnet#101444 (comment)).

Supports: dotnet#91826
  • Loading branch information
sbomer authored and steveharter committed May 28, 2024
1 parent 7c4d06e commit 5fdc9b8
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 30 deletions.
2 changes: 1 addition & 1 deletion eng/pipelines/common/templates/pipeline-with-resources.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ extends:

containers:
linux_arm:
image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-cross-arm-net9.0-20240507035943-1390eea
image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-cross-arm-net9.0
env:
ROOTFS_DIR: /crossrootfs/arm

Expand Down
4 changes: 2 additions & 2 deletions eng/pipelines/coreclr/templates/helix-queues-setup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ jobs:
# Linux arm
- ${{ if eq(parameters.platform, 'linux_arm') }}:
- ${{ if eq(variables['System.TeamProject'], 'public') }}:
- (Ubuntu.1804.Arm32.Open)Ubuntu.2004.Armarch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-18.04-helix-arm32v7
- (Debian.12.Arm32.Open)Ubuntu.2004.ArmArch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-12-helix-arm32v7
- ${{ if eq(variables['System.TeamProject'], 'internal') }}:
- (Ubuntu.1804.Arm32)Ubuntu.2004.Armarch@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-18.04-helix-arm32v7
- (Debian.12.Arm32)Ubuntu.2004.ArmArch@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-12-helix-arm32v7

# Linux arm64
- ${{ if eq(parameters.platform, 'linux_arm64') }}:
Expand Down
2 changes: 1 addition & 1 deletion eng/pipelines/libraries/helix-queues-setup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
# Linux arm
- ${{ if eq(parameters.platform, 'linux_arm') }}:
- ${{ if or(eq(parameters.jobParameters.isExtraPlatformsBuild, true), eq(parameters.jobParameters.includeAllPlatforms, true)) }}:
- (Debian.11.Arm32.Open)Ubuntu.2004.ArmArch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-11-helix-arm32v7
- (Debian.12.Arm32.Open)Ubuntu.2004.ArmArch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-12-helix-arm32v7

# Linux armv6
- ${{ if eq(parameters.platform, 'linux_armv6') }}:
Expand Down
14 changes: 14 additions & 0 deletions src/native/libs/System.Security.Cryptography.Native/openssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -964,6 +964,20 @@ int32_t CryptoNative_X509StoreSetVerifyTime(X509_STORE* ctx,
return 0;
}

#if defined(FEATURE_DISTRO_AGNOSTIC_SSL) && defined(TARGET_ARM) && defined(TARGET_LINUX)
if (g_libSslUses32BitTime)
{
if (verifyTime > INT_MAX || verifyTime < INT_MIN)
{
return 0;
}

// Cast to a signature that takes a 32-bit value for the time.
((void (*)(X509_VERIFY_PARAM*, int32_t))(void*)(X509_VERIFY_PARAM_set_time))(verifyParams, (int32_t)verifyTime);
return 1;
}
#endif

X509_VERIFY_PARAM_set_time(verifyParams, verifyTime);
return 1;
}
Expand Down
35 changes: 35 additions & 0 deletions src/native/libs/System.Security.Cryptography.Native/opensslshim.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ FOR_ALL_OPENSSL_FUNCTIONS
#undef LIGHTUP_FUNCTION
#undef REQUIRED_FUNCTION_110
#undef REQUIRED_FUNCTION
#if defined(TARGET_ARM) && defined(TARGET_LINUX)
TYPEOF(OPENSSL_gmtime) OPENSSL_gmtime_ptr;
#endif

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

#if defined(TARGET_ARM) && defined(TARGET_LINUX)
// We support ARM32 linux distros that have Y2038-compatible glibc (those which support _TIME_BITS).
// Some such distros have not yet switched to _TIME_BITS=64 by default, so we may be running against an openssl
// that expects 32-bit time_t even though our time_t is 64-bit.
// This can be deleted once the minimum supported Linux Arm32 distros are
// at least Debian 13 and Ubuntu 24.04.
bool g_libSslUses32BitTime = false;
#endif

static void DlOpen(const char* libraryName)
{
void* libsslNew = dlopen(libraryName, RTLD_LAZY);
Expand Down Expand Up @@ -205,6 +217,10 @@ void InitializeOpenSSLShim(void)
#undef LIGHTUP_FUNCTION
#undef REQUIRED_FUNCTION_110
#undef REQUIRED_FUNCTION
#if defined(TARGET_ARM) && defined(TARGET_LINUX)
if (!(OPENSSL_gmtime_ptr = (TYPEOF(OPENSSL_gmtime))(dlsym(libssl, "OPENSSL_gmtime")))) { fprintf(stderr, "Cannot get required symbol OPENSSL_gmtime from libssl\n"); abort(); }
#endif


// Sanity check that we have at least one functioning way of reporting errors.
if (ERR_put_error_ptr == &local_ERR_put_error)
Expand All @@ -215,4 +231,23 @@ void InitializeOpenSSLShim(void)
abort();
}
}

#if defined(TARGET_ARM) && defined(TARGET_LINUX)
// This value will represent a time in year 2038 if 64-bit time is used,
// or 1901 if the lower 32 bits are interpreted as a 32-bit time_t value.
time_t timeVal = (time_t)INT_MAX + 1;
struct tm tmVal = { 0 };

// Detect whether openssl is using 32-bit or 64-bit time_t.
// If it uses 32-bit time_t, little-endianness means that the pointer
// will be interpreted as a pointer to the lower 32 bits of timeVal.
// tm_year is the number of years since 1900.
if (!OPENSSL_gmtime(&timeVal, &tmVal) || (tmVal.tm_year != 138 && tmVal.tm_year != 1))
{
fprintf(stderr, "Cannot determine the time_t size used by libssl\n");
abort();
}

g_libSslUses32BitTime = (tmVal.tm_year == 1);
#endif
}
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ int EVP_DigestSqueeze(EVP_MD_CTX *ctx, unsigned char *out, size_t outlen);

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

#if defined(FEATURE_DISTRO_AGNOSTIC_SSL) && defined(TARGET_ARM) && defined(TARGET_LINUX)
extern bool g_libSslUses32BitTime;
#endif

// List of all functions from the libssl that are used in the System.Security.Cryptography.Native.
// Forgetting to add a function here results in build failure with message reporting the function
// that needs to be added.
Expand Down Expand Up @@ -618,7 +622,6 @@ int EVP_DigestSqueeze(EVP_MD_CTX *ctx, unsigned char *out, size_t outlen);
REQUIRED_FUNCTION(SSL_version) \
FALLBACK_FUNCTION(X509_check_host) \
REQUIRED_FUNCTION(X509_check_purpose) \
REQUIRED_FUNCTION(X509_cmp_current_time) \
REQUIRED_FUNCTION(X509_cmp_time) \
REQUIRED_FUNCTION(X509_CRL_free) \
FALLBACK_FUNCTION(X509_CRL_get0_nextUpdate) \
Expand Down Expand Up @@ -717,7 +720,9 @@ FOR_ALL_OPENSSL_FUNCTIONS
#undef LIGHTUP_FUNCTION
#undef REQUIRED_FUNCTION_110
#undef REQUIRED_FUNCTION

#if defined(TARGET_ARM) && defined(TARGET_LINUX)
extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr;
#endif
// Redefine all calls to OpenSSL functions as calls through pointers that are set
// to the functions from the libssl.so selected by the shim.
#define a2d_ASN1_OBJECT a2d_ASN1_OBJECT_ptr
Expand Down Expand Up @@ -1018,6 +1023,7 @@ FOR_ALL_OPENSSL_FUNCTIONS
#define OCSP_RESPONSE_new OCSP_RESPONSE_new_ptr
#define OPENSSL_add_all_algorithms_conf OPENSSL_add_all_algorithms_conf_ptr
#define OPENSSL_cleanse OPENSSL_cleanse_ptr
#define OPENSSL_gmtime OPENSSL_gmtime_ptr
#define OPENSSL_init_ssl OPENSSL_init_ssl_ptr
#define OPENSSL_sk_free OPENSSL_sk_free_ptr
#define OPENSSL_sk_new_null OPENSSL_sk_new_null_ptr
Expand Down Expand Up @@ -1149,7 +1155,6 @@ FOR_ALL_OPENSSL_FUNCTIONS
#define TLS_method TLS_method_ptr
#define X509_check_host X509_check_host_ptr
#define X509_check_purpose X509_check_purpose_ptr
#define X509_cmp_current_time X509_cmp_current_time_ptr
#define X509_cmp_time X509_cmp_time_ptr
#define X509_CRL_free X509_CRL_free_ptr
#define X509_CRL_get0_nextUpdate X509_CRL_get0_nextUpdate_ptr
Expand Down
53 changes: 30 additions & 23 deletions src/native/libs/System.Security.Cryptography.Native/pal_x509.c
Original file line number Diff line number Diff line change
Expand Up @@ -893,14 +893,12 @@ static OCSP_CERTID* MakeCertId(X509* subject, X509* issuer)
return OCSP_cert_to_id(EVP_sha1(), subject, issuer);
}

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

static X509VerifyStatusCode CheckOcspGetExpiry(OCSP_REQUEST* req,
Expand Down Expand Up @@ -960,28 +958,37 @@ static X509VerifyStatusCode CheckOcspGetExpiry(OCSP_REQUEST* req,

if (OCSP_resp_find_status(basicResp, certId, &status, NULL, NULL, &thisupd, &nextupd))
{
// X509_cmp_current_time uses 0 for error already, so we can use it when there's a null value.
// 1 means the nextupd value is in the future, -1 means it is now-or-in-the-past.
// Following with OpenSSL conventions, we'll accept "now" as "the past".
int nextUpdComparison = nextupd == NULL ? 0 : X509_cmp_current_time(nextupd);

// Un-revoking is rare, so reporting revoked on an expired response has a low chance
// of a false-positive.
//
// For non-revoked responses, a next-update value in the past counts as expired.
if (status == V_OCSP_CERTSTATUS_REVOKED)
{
ret = PAL_X509_V_ERR_CERT_REVOKED;
}
else
time_t currentTime = time(NULL);
int nextUpdComparison = 0;
#if defined(FEATURE_DISTRO_AGNOSTIC_SSL) && defined(TARGET_ARM) && defined(TARGET_LINUX)
// If openssl uses 32-bit time_t and the current time doesn't fit in 32 bits,
// skip checking the status/nextupd, and fall through to return PAL_X509_V_ERR_UNABLE_TO_GET_CRL.
if (!g_libSslUses32BitTime || (currentTime >= INT_MIN && currentTime <= INT_MAX))
#endif
{
if (nextupd != NULL && nextUpdComparison <= 0)
// X509_cmp_current_time uses 0 for error already, so we can use it when there's a null value.
// 1 means the nextupd value is in the future, -1 means it is now-or-in-the-past.
// Following with OpenSSL conventions, we'll accept "now" as "the past".
nextUpdComparison = nextupd == NULL ? 0 : X509_cmp_time(nextupd, &currentTime);

// Un-revoking is rare, so reporting revoked on an expired response has a low chance
// of a false-positive.
//
// For non-revoked responses, a next-update value in the past counts as expired.
if (status == V_OCSP_CERTSTATUS_REVOKED)
{
ret = PAL_X509_V_ERR_CRL_HAS_EXPIRED;
ret = PAL_X509_V_ERR_CERT_REVOKED;
}
else if (status == V_OCSP_CERTSTATUS_GOOD)
else
{
ret = PAL_X509_V_OK;
if (nextupd != NULL && nextUpdComparison <= 0)
{
ret = PAL_X509_V_ERR_CRL_HAS_EXPIRED;
}
else if (status == V_OCSP_CERTSTATUS_GOOD)
{
ret = PAL_X509_V_OK;
}
}
}

Expand All @@ -997,7 +1004,7 @@ static X509VerifyStatusCode CheckOcspGetExpiry(OCSP_REQUEST* req,
thisupd != NULL &&
nextUpdComparison > 0)
{
time_t oldest = GetIssuanceWindowStart();
time_t oldest = GetIssuanceWindowStart(currentTime);

if (X509_cmp_time(thisupd, &oldest) > 0)
{
Expand Down

0 comments on commit 5fdc9b8

Please sign in to comment.