diff --git a/VERSION.txt b/VERSION.txt index d914dc43a..36769b920 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.20260708.1 +1.20260708.2 diff --git a/src/bindings/python/CMakeLists.txt b/src/bindings/python/CMakeLists.txt index cf9f28444..bb581eccb 100644 --- a/src/bindings/python/CMakeLists.txt +++ b/src/bindings/python/CMakeLists.txt @@ -18,7 +18,8 @@ set(PYTHON_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/file.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/glibc.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/glibc_arc4random.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/glibc_strtol.cpp ${CMAKE_CURRENT_SOURCE_DIR}/logger.cpp ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp ${CMAKE_CURRENT_SOURCE_DIR}/sed.cpp @@ -34,27 +35,44 @@ if(LIBOPENCOR_PYTHON_BINDINGS) set(PYTHON_BINDINGS_DIR ${CMAKE_BINARY_DIR}/tests/bindings/python CACHE INTERNAL "Python bindings directory.") - # Check whether we need to provide a glibc compatibility wrapper for __isoc23_strtol(), __isoc23_strtoll(), - # __isoc23_strtoul(), and __isoc23_strtoull(). - # Note: indeed, they are used by our prebuilt LLVM+Clang and libssh2 libraries which are compiled using glibc 2.38+ - # (Ubuntu 24.04 LTS on GitHub Actions), but there are currently no versions of manylinux that uses glibc - # 2.38+, so we need to provide a compatibility wrapper for these functions in order to be able to build our - # Python wheels. - - include(CheckSymbolExists) - - check_symbol_exists(__isoc23_strtol stdlib.h LIBOPENCOR_GLIBC_HAS_ISOC23_STRTOL) - check_symbol_exists(__isoc23_strtoll stdlib.h LIBOPENCOR_GLIBC_HAS_ISOC23_STRTOLL) - check_symbol_exists(__isoc23_strtoul stdlib.h LIBOPENCOR_GLIBC_HAS_ISOC23_STRTOUL) - check_symbol_exists(__isoc23_strtoull stdlib.h LIBOPENCOR_GLIBC_HAS_ISOC23_STRTOULL) + # Check whether we need to provide glibc compatibility wrappers for __isoc23_strtol(), __isoc23_strtoll(), + # __isoc23_strtoul(), __isoc23_strtoull() (glibc 2.38+), and arc4random(), arc4random_buf(), arc4random_uniform() + # (glibc 2.36+). These are referenced by our prebuilt LLVM+Clang and libssh2 libraries which are compiled using + # glibc 2.38+ (Ubuntu 24.04 LTS on GitHub Actions), but there are currently no versions of manylinux that use glibc + # 2.38+, so we need compatibility wrappers to build Python wheels. Each symbol group is guarded independently so + # that only the wrappers for actually missing functions are compiled into the module, avoiding weak-symbol + # interposition against the native glibc implementation. set(ACTUAL_PYTHON_SOURCE_FILES ${PYTHON_SOURCE_FILES}) - if( LIBOPENCOR_GLIBC_HAS_ISOC23_STRTOL - AND LIBOPENCOR_GLIBC_HAS_ISOC23_STRTOLL - AND LIBOPENCOR_GLIBC_HAS_ISOC23_STRTOUL - AND LIBOPENCOR_GLIBC_HAS_ISOC23_STRTOULL) - list(REMOVE_ITEM ACTUAL_PYTHON_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/glibc.cpp) + if(WIN32 OR APPLE) + list(REMOVE_ITEM ACTUAL_PYTHON_SOURCE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/glibc_arc4random.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/glibc_strtol.cpp) + else() + include(CheckSymbolExists) + + check_symbol_exists(arc4random stdlib.h LIBOPENCOR_GLIBC_HAS_ARC4RANDOM) + check_symbol_exists(arc4random_buf stdlib.h LIBOPENCOR_GLIBC_HAS_ARC4RANDOM_BUF) + check_symbol_exists(arc4random_uniform stdlib.h LIBOPENCOR_GLIBC_HAS_ARC4RANDOM_UNIFORM) + + if( LIBOPENCOR_GLIBC_HAS_ARC4RANDOM + AND LIBOPENCOR_GLIBC_HAS_ARC4RANDOM_BUF + AND LIBOPENCOR_GLIBC_HAS_ARC4RANDOM_UNIFORM) + list(REMOVE_ITEM ACTUAL_PYTHON_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/glibc_arc4random.cpp) + endif() + + check_symbol_exists(__isoc23_strtol stdlib.h LIBOPENCOR_GLIBC_HAS_ISOC23_STRTOL) + check_symbol_exists(__isoc23_strtoll stdlib.h LIBOPENCOR_GLIBC_HAS_ISOC23_STRTOLL) + check_symbol_exists(__isoc23_strtoul stdlib.h LIBOPENCOR_GLIBC_HAS_ISOC23_STRTOUL) + check_symbol_exists(__isoc23_strtoull stdlib.h LIBOPENCOR_GLIBC_HAS_ISOC23_STRTOULL) + + if( LIBOPENCOR_GLIBC_HAS_ISOC23_STRTOL + AND LIBOPENCOR_GLIBC_HAS_ISOC23_STRTOLL + AND LIBOPENCOR_GLIBC_HAS_ISOC23_STRTOUL + AND LIBOPENCOR_GLIBC_HAS_ISOC23_STRTOULL) + list(REMOVE_ITEM ACTUAL_PYTHON_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/glibc_strtol.cpp) + endif() endif() # Build our Python bindings. diff --git a/src/bindings/python/glibc_arc4random.cpp b/src/bindings/python/glibc_arc4random.cpp new file mode 100644 index 000000000..e976b6dea --- /dev/null +++ b/src/bindings/python/glibc_arc4random.cpp @@ -0,0 +1,122 @@ +/* +Copyright libOpenCOR contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include +#include +#include +#include +#include + +#if defined(__linux__) && defined(__GNUC__) +// arc4random(), arc4random_buf(), and arc4random_uniform() were added in glibc 2.36, so we need to provide a +// compatibility implementation on older glibc versions. + +extern "C" { +__attribute__((weak)) uint32_t arc4random() +{ + int fd; + + do { + fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC); + } while ((fd < 0) && (errno == EINTR)); + + if (fd < 0) { + __builtin_abort(); + } + + uint32_t res = 0; + size_t offset = 0; + + while (offset < sizeof(res)) { + ssize_t bytesRead = read(fd, reinterpret_cast(&res) + offset, sizeof(res) - offset); + + if (bytesRead > 0) { + offset += static_cast(bytesRead); + } else if ((bytesRead < 0) && (errno == EINTR)) { + continue; + } else { + // EOF or unrecoverable error. + + break; + } + } + + close(fd); + + if (offset != sizeof(res)) { + __builtin_abort(); + } + + return res; +} + +__attribute__((weak)) void arc4random_buf(void *pBuf, size_t pSize) +{ + if (pSize == 0) { + return; + } + + int fd; + + do { + fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC); + } while ((fd < 0) && (errno == EINTR)); + + if (fd < 0) { + __builtin_abort(); + } + + size_t offset = 0; + + while (offset < pSize) { + ssize_t bytesRead = read(fd, static_cast(pBuf) + offset, pSize - offset); + + if (bytesRead > 0) { + offset += static_cast(bytesRead); + } else if ((bytesRead < 0) && (errno == EINTR)) { + continue; + } else { + // EOF or unrecoverable error. + + break; + } + } + + close(fd); + + if (offset != pSize) { + __builtin_abort(); + } +} + +__attribute__((weak)) uint32_t arc4random_uniform(uint32_t pUpperBound) +{ + if (pUpperBound < 2) { + return 0; + } + + uint32_t rejectionThreshold = -pUpperBound % pUpperBound; + uint32_t number; + + do { + number = arc4random(); + } while (number < rejectionThreshold); + + return number % pUpperBound; +} +} + +#endif diff --git a/src/bindings/python/glibc.cpp b/src/bindings/python/glibc_strtol.cpp similarity index 57% rename from src/bindings/python/glibc.cpp rename to src/bindings/python/glibc_strtol.cpp index b0f681788..c305765d6 100644 --- a/src/bindings/python/glibc.cpp +++ b/src/bindings/python/glibc_strtol.cpp @@ -14,13 +14,27 @@ See the License for the specific language governing permissions and limitations under the License. */ +#include +#include + #if defined(__linux__) && defined(__GNUC__) // On glibc 2.38+, strtol(), strtoll(), strtoul(), and strtoull() are redirected to __isoc23_strtol(), // __isoc23_strtoll(), __isoc23_strtoul(), and __isoc23_strtoull(), respectively, either via preprocessor macro (Intel) -// or via __asm__("__isoc23_*") on the declaration (ARM). The latter is a compiler-level redirect that cannot be undone -// with #undef, so calling strtol() inside our __isoc23_strtol() wrapper would cause infinite recursion on ARM. To -// bypass this, we forward-declare the underlying functions ourselves without the compiler-level redirect, giving us a -// direct reference to glibc's implementation unaffected by any asm redirect on the standard names. +// or via __asm__("__isoc23_*") on the declaration (ARM). Both approaches would cause any call to strtol() inside our +// __isoc23_strtol() wrapper to resolve back to __isoc23_strtol() (either via macro expansion on Intel or via the asm +// redirect on ARM), leading to infinite recursion. To bypass this, we forward-declare the underlying strtol functions +// ourselves without the compiler-level redirect, giving us a direct reference to glibc's implementation that is +// unaffected by any macro or asm redirect on the standard names. +// +// Important: we do NOT include because on Intel, glibc redirects using preprocessor macros: +// #define strtol __isoc23_strtol +// #define strtoll __isoc23_strtoll +// #define strtoul __isoc23_strtoul +// #define strtoull __isoc23_strtoull +// which would macro-expand both our forward declarations and the return strtol() calls inside our wrappers, making +// them recursive. On ARM, glibc instead uses __asm__("__isoc23_strtol") on the declarations in , which would +// conflict with our own forward declarations (different asm labels on the same symbol). Either way, omitting +// entirely avoids both problems while still allowing us to forward-declare the real symbols. extern "C" { long strtol(const char *pString, char **pEndPtr, int pBase);