diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 3bd859a378..e1f57362a9 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -10,36 +10,40 @@ jobs: matrix: compiler: [gcc, clang] python-ruby-version: - - {python: '3.11', ruby: '3.1'} - - {python: '3.11', ruby: '3.1', other: 'test-flags-override'} - - {python: '3.11', ruby: '3.1', other: 'test-debug'} - - {python: '3.11', ruby: '3.1', other: 'linker-bfd'} - - {python: '3.11', ruby: '3.1', other: 'linker-gold'} - # Test several Python versions with the latest Ruby version - - {python: '3.10', ruby: '3.1'} - - {python: '3.9', ruby: '3.1'} - - {python: '3.8', ruby: '3.1'} - - {python: '3.7', ruby: '3.1'} - - {python: 'pypy3.7', ruby: '3.1'} + - {python: '3.12', ruby: '3.3'} + - {python: '3.12', ruby: '3.3', other: 'test-flags-override'} + - {python: '3.12', ruby: '3.3', other: 'test-debug'} + - {python: '3.12', ruby: '3.3', other: 'linker-bfd'} + - {python: '3.12', ruby: '3.3', other: 'linker-gold'} + - {python: '3.12', ruby: '3.3', other: 'pcre2-dlsym'} + # Test several Python versions with the latest Ruby version + - {python: '3.11', ruby: '3.3'} + - {python: '3.10', ruby: '3.3'} + - {python: '3.9', ruby: '3.3'} + - {python: '3.8', ruby: '3.3'} + - {python: '3.7', ruby: '3.3'} + - {python: 'pypy3.7', ruby: '3.3'} # Test several Ruby versions with the latest Python version - - {python: '3.11', ruby: '3.0'} - - {python: '3.11', ruby: '2.7'} - - {python: '3.11', ruby: '2.6'} - - {python: '3.11', ruby: '2.5'} + - {python: '3.12', ruby: '3.2'} + - {python: '3.12', ruby: '3.1'} + - {python: '3.12', ruby: '3.0'} + - {python: '3.12', ruby: '2.7'} + - {python: '3.12', ruby: '2.6'} + - {python: '3.12', ruby: '2.5'} exclude: - compiler: clang - python-ruby-version: {python: '3.11', ruby: '3.1', other: 'linker-bfd'} + python-ruby-version: {python: '3.12', ruby: '3.3', other: 'linker-bfd'} - compiler: clang - python-ruby-version: {python: '3.11', ruby: '3.1', other: 'linker-gold'} + python-ruby-version: {python: '3.12', ruby: '3.3', other: 'linker-gold'} include: - compiler: gcc - python-ruby-version: {python: '3.11', ruby: '3.1', other: 'sanitizers'} + python-ruby-version: {python: '3.12', ruby: '3.3', other: 'sanitizers'} steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-ruby-version.python }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-ruby-version.python }} @@ -67,7 +71,7 @@ jobs: swig \ xmlto - pip install flake8 + pip install flake8 setuptools - name: Install Clang if: ${{ matrix.compiler == 'clang' }} @@ -85,6 +89,8 @@ jobs: CC="$CC -fuse-ld=bfd" elif [ "${{ matrix.python-ruby-version.other }}" = "linker-gold" ] ; then CC="$CC -fuse-ld=gold" + elif [ "${{ matrix.python-ruby-version.other }}" = "pcre2-dlsym" ] ; then + echo "USE_PCRE2_DLSYM=y" >> $GITHUB_ENV fi # https://bugs.ruby-lang.org/issues/18616 # https://github.com/llvm/llvm-project/issues/49958 diff --git a/libselinux/Makefile b/libselinux/Makefile index 6d9e273641..254e4b2649 100644 --- a/libselinux/Makefile +++ b/libselinux/Makefile @@ -24,9 +24,15 @@ endif export DISABLE_SETRANS DISABLE_RPM DISABLE_FLAGS ANDROID_HOST DISABLE_X11 LABEL_BACKEND_ANDROID USE_PCRE2 ?= y +USE_PCRE2_DLSYM ?= n ifeq ($(USE_PCRE2),y) - PCRE_MODULE := libpcre2-8 - PCRE_CFLAGS := -DUSE_PCRE2 -DPCRE2_CODE_UNIT_WIDTH=8 + ifeq ($(USE_PCRE2_DLSYM),n) + PCRE_CFLAGS := -DUSE_PCRE2 -DPCRE2_CODE_UNIT_WIDTH=8 + PCRE_MODULE := libpcre2-8 + else + PCRE_CFLAGS := -DUSE_PCRE2 -DPCRE2_CODE_UNIT_WIDTH=8 -DUSE_PCRE2_DLSYM + PCRE_MODULE := + endif else PCRE_MODULE := libpcre endif diff --git a/libselinux/src/regex.c b/libselinux/src/regex.c index 88d82fedb2..49c2d42125 100644 --- a/libselinux/src/regex.c +++ b/libselinux/src/regex.c @@ -5,6 +5,7 @@ #include #include "regex.h" +#include "regex_dlsym.h" #include "label_file.h" #include "selinux_internal.h" @@ -29,6 +30,12 @@ #endif +/** + * This constructor function allocates a buffer for a regex_data structure. + * The buffer is being initialized with zeroes. + */ +static struct regex_data *regex_data_create(void); + #ifdef USE_PCRE2 char const *regex_arch_string(void) { @@ -75,6 +82,9 @@ int regex_prepare_data(struct regex_data **regex, char const *pattern_string, { memset(errordata, 0, sizeof(struct regex_error_data)); + if (regex_pcre2_load() < 0) + return -1; + *regex = regex_data_create(); if (!(*regex)) return -1; @@ -104,7 +114,12 @@ int regex_prepare_data(struct regex_data **regex, char const *pattern_string, char const *regex_version(void) { static char version_buf[256]; - size_t len = pcre2_config(PCRE2_CONFIG_VERSION, NULL); + size_t len; + + if (regex_pcre2_load() < 0) + return NULL; + + len = pcre2_config(PCRE2_CONFIG_VERSION, NULL); if (len <= 0 || len > sizeof(version_buf)) return NULL; @@ -119,6 +134,10 @@ int regex_load_mmap(struct mmap_area *mmap_area, struct regex_data **regex, uint32_t entry_len; *regex_compiled = false; + + if (regex_pcre2_load() < 0) + return -1; + rc = next_entry(&entry_len, mmap_area, sizeof(uint32_t)); if (rc < 0) return -1; @@ -164,7 +183,7 @@ int regex_load_mmap(struct mmap_area *mmap_area, struct regex_data **regex, return -1; } -int regex_writef(struct regex_data *regex, FILE *fp, int do_write_precompregex) +int regex_writef(const struct regex_data *regex, FILE *fp, int do_write_precompregex) { int rc = 0; size_t len; @@ -172,6 +191,9 @@ int regex_writef(struct regex_data *regex, FILE *fp, int do_write_precompregex) uint32_t to_write = 0; PCRE2_UCHAR *bytes = NULL; + if (regex_pcre2_load() < 0) + return -1; + if (do_write_precompregex) { /* encode the pattern for serialization */ rc = pcre2_serialize_encode((const pcre2_code **)®ex->regex, @@ -206,6 +228,9 @@ int regex_writef(struct regex_data *regex, FILE *fp, int do_write_precompregex) void regex_data_free(struct regex_data *regex) { + if (regex_pcre2_load() < 0) + return; + if (regex) { if (regex->regex) pcre2_code_free(regex->regex); @@ -224,6 +249,10 @@ int regex_match(struct regex_data *regex, char const *subject, int partial) { int rc; pcre2_match_data *match_data; + + if (regex_pcre2_load() < 0) + return REGEX_ERROR; + __pthread_mutex_lock(®ex->match_mutex); #ifdef AGGRESSIVE_FREE_AFTER_REGEX_MATCH @@ -269,10 +298,14 @@ int regex_match(struct regex_data *regex, char const *subject, int partial) * Preferably, this function would be replaced with an algorithm that computes * the equivalence of the automatons systematically. */ -int regex_cmp(struct regex_data *regex1, struct regex_data *regex2) +int regex_cmp(const struct regex_data *regex1, const struct regex_data *regex2) { int rc; size_t len1, len2; + + if (regex_pcre2_load() < 0) + return SELABEL_INCOMPARABLE; + rc = pcre2_pattern_info(regex1->regex, PCRE2_INFO_SIZE, &len1); assert(rc == 0); rc = pcre2_pattern_info(regex2->regex, PCRE2_INFO_SIZE, &len2); @@ -283,7 +316,7 @@ int regex_cmp(struct regex_data *regex1, struct regex_data *regex2) return SELABEL_EQUAL; } -struct regex_data *regex_data_create(void) +static struct regex_data *regex_data_create(void) { struct regex_data *regex_data = (struct regex_data *)calloc(1, sizeof(struct regex_data)); @@ -407,7 +440,7 @@ int regex_load_mmap(struct mmap_area *mmap_area, struct regex_data **regex, return -1; } -static inline pcre_extra *get_pcre_extra(struct regex_data *regex) +static inline const pcre_extra *get_pcre_extra(const struct regex_data *regex) { if (!regex) return NULL; if (regex->owned) { @@ -419,14 +452,14 @@ static inline pcre_extra *get_pcre_extra(struct regex_data *regex) } } -int regex_writef(struct regex_data *regex, FILE *fp, +int regex_writef(const struct regex_data *regex, FILE *fp, int do_write_precompregex __attribute__((unused))) { int rc; size_t len; uint32_t to_write; size_t size; - pcre_extra *sd = get_pcre_extra(regex); + const pcre_extra *sd = get_pcre_extra(regex); /* determine the size of the pcre data in bytes */ rc = pcre_fullinfo(regex->regex, NULL, PCRE_INFO_SIZE, &size); @@ -510,7 +543,7 @@ int regex_match(struct regex_data *regex, char const *subject, int partial) * Preferably, this function would be replaced with an algorithm that computes * the equivalence of the automatons systematically. */ -int regex_cmp(struct regex_data *regex1, struct regex_data *regex2) +int regex_cmp(const struct regex_data *regex1, const struct regex_data *regex2) { int rc; size_t len1, len2; @@ -524,7 +557,7 @@ int regex_cmp(struct regex_data *regex1, struct regex_data *regex2) return SELABEL_EQUAL; } -struct regex_data *regex_data_create(void) +static struct regex_data *regex_data_create(void) { return (struct regex_data *)calloc(1, sizeof(struct regex_data)); } @@ -540,6 +573,11 @@ void regex_format_error(struct regex_error_data const *error_data, char *buffer, size_t pos = 0; if (!buffer || !buf_size) return; +#ifdef USE_PCRE2 + rc = regex_pcre2_load(); + if (rc < 0) + return; +#endif rc = snprintf(buffer, buf_size, "REGEX back-end error: "); if (rc < 0) /* diff --git a/libselinux/src/regex.h b/libselinux/src/regex.h index 2dfa253420..0bff344deb 100644 --- a/libselinux/src/regex.h +++ b/libselinux/src/regex.h @@ -55,11 +55,6 @@ char const *regex_arch_string(void) ; * It may return NULL on error. */ char const *regex_version(void) ; -/** - * This constructor function allocates a buffer for a regex_data structure. - * The buffer is being initialized with zeroes. - */ -struct regex_data *regex_data_create(void) ; /** * This complementary destructor function frees the a given regex_data buffer. * It also frees any non NULL member pointers with the appropriate pcreX_X_free @@ -120,7 +115,7 @@ int regex_load_mmap(struct mmap_area *map_area, * @arg do_write_precompregex If non-zero precompiled patterns are written to * the output file (ignored by PCRE1 back-end). */ -int regex_writef(struct regex_data *regex, FILE *fp, +int regex_writef(const struct regex_data *regex, FILE *fp, int do_write_precompregex) ; /** * This function applies a precompiled pattern to a subject string and @@ -149,7 +144,7 @@ int regex_match(struct regex_data *regex, char const *subject, * the same * @retval SELABEL_INCOMPARABLE otherwise */ -int regex_cmp(struct regex_data *regex1, struct regex_data *regex2) ; +int regex_cmp(const struct regex_data *regex1, const struct regex_data *regex2) ; /** * This function takes the error data returned by regex_prepare_data and turns * it in to a human readable error message. diff --git a/libselinux/src/regex_dlsym.c b/libselinux/src/regex_dlsym.c new file mode 100644 index 0000000000..c20ad72dc4 --- /dev/null +++ b/libselinux/src/regex_dlsym.c @@ -0,0 +1,110 @@ +#include "regex_dlsym.h" + +#ifdef USE_PCRE2_DLSYM + +#include "callbacks.h" +#include "selinux_internal.h" + +#include +#include + + +#define DLSYM_FUNC(symbol) typeof(symbol)* sym_##symbol = NULL + +#define DLSYM_RESOLVE(handle, symbol) do { \ + sym_##symbol = dlsym(handle, #symbol); \ + if (!sym_##symbol) { \ + selinux_log(SELINUX_ERROR, "Failed to resolve symbol %s: %s\n", #symbol, dlerror()); \ + goto err; \ + } \ +} while(0) + +DLSYM_FUNC(pcre2_code_free_8); +DLSYM_FUNC(pcre2_compile_8); +DLSYM_FUNC(pcre2_config_8); +DLSYM_FUNC(pcre2_get_error_message_8); +DLSYM_FUNC(pcre2_match_8); +DLSYM_FUNC(pcre2_match_data_create_from_pattern_8); +DLSYM_FUNC(pcre2_match_data_free_8); +DLSYM_FUNC(pcre2_pattern_info_8); +DLSYM_FUNC(pcre2_serialize_decode_8); +DLSYM_FUNC(pcre2_serialize_encode_8); +DLSYM_FUNC(pcre2_serialize_free_8); +DLSYM_FUNC(pcre2_serialize_get_number_of_codes_8); + +static void *libpcre2_handle = NULL; +static pthread_mutex_t libpcre2_lock = PTHREAD_MUTEX_INITIALIZER; + + +static void *load_impl(void) { + void *handle; + + handle = dlopen("libpcre2-8.so", RTLD_LAZY); + if (!handle) { + handle = dlopen("libpcre2-8.so.0", RTLD_LAZY); + if (!handle) { + selinux_log(SELINUX_ERROR, "Failed to load libpcre2-8: %s\n", dlerror()); + return NULL; + } + } + + DLSYM_RESOLVE(handle, pcre2_code_free_8); + DLSYM_RESOLVE(handle, pcre2_compile_8); + DLSYM_RESOLVE(handle, pcre2_config_8); + DLSYM_RESOLVE(handle, pcre2_get_error_message_8); + DLSYM_RESOLVE(handle, pcre2_match_8); + DLSYM_RESOLVE(handle, pcre2_match_data_create_from_pattern_8); + DLSYM_RESOLVE(handle, pcre2_match_data_free_8); + DLSYM_RESOLVE(handle, pcre2_pattern_info_8); + DLSYM_RESOLVE(handle, pcre2_serialize_decode_8); + DLSYM_RESOLVE(handle, pcre2_serialize_encode_8); + DLSYM_RESOLVE(handle, pcre2_serialize_free_8); + DLSYM_RESOLVE(handle, pcre2_serialize_get_number_of_codes_8); + + return handle; + +err: + sym_pcre2_code_free_8 = NULL; + sym_pcre2_compile_8 = NULL; + sym_pcre2_config_8 = NULL; + sym_pcre2_get_error_message_8 = NULL; + sym_pcre2_match_8 = NULL; + sym_pcre2_match_data_create_from_pattern_8 = NULL; + sym_pcre2_match_data_free_8 = NULL; + sym_pcre2_pattern_info_8 = NULL; + sym_pcre2_serialize_decode_8 = NULL; + sym_pcre2_serialize_encode_8 = NULL; + sym_pcre2_serialize_free_8 = NULL; + sym_pcre2_serialize_get_number_of_codes_8 = NULL; + + if (handle) + dlclose(handle); + return NULL; +} + +int regex_pcre2_load(void) { + void *handle; + + handle = __atomic_load_n(&libpcre2_handle, __ATOMIC_ACQUIRE); + if (handle) + return 0; + + __pthread_mutex_lock(&libpcre2_lock); + + /* Check if another thread validated the context while we waited on the mutex */ + handle = __atomic_load_n(&libpcre2_handle, __ATOMIC_ACQUIRE); + if (handle) { + __pthread_mutex_unlock(&libpcre2_lock); + return 0; + } + + handle = load_impl(); + if (handle) + __atomic_store_n(&libpcre2_handle, handle, __ATOMIC_RELEASE); + + __pthread_mutex_unlock(&libpcre2_lock); + + return handle ? 0 : -1; +} + +#endif /* USE_PCRE2_DLSYM */ diff --git a/libselinux/src/regex_dlsym.h b/libselinux/src/regex_dlsym.h new file mode 100644 index 0000000000..3f86246c36 --- /dev/null +++ b/libselinux/src/regex_dlsym.h @@ -0,0 +1,65 @@ +#ifndef LIBSELINUX_REGEX_DLSYM_H +#define LIBSELINUX_REGEX_DLSYM_H + +#ifdef USE_PCRE2 + +#ifdef USE_PCRE2_DLSYM + +#include + +#include + + +int regex_pcre2_load(void); + +#define DLSYM_PROTO(symbol) extern typeof(symbol)* sym_##symbol +DLSYM_PROTO(pcre2_code_free_8); +DLSYM_PROTO(pcre2_compile_8); +DLSYM_PROTO(pcre2_config_8); +DLSYM_PROTO(pcre2_get_error_message_8); +DLSYM_PROTO(pcre2_match_8); +DLSYM_PROTO(pcre2_match_data_create_from_pattern_8); +DLSYM_PROTO(pcre2_match_data_free_8); +DLSYM_PROTO(pcre2_pattern_info_8); +DLSYM_PROTO(pcre2_serialize_decode_8); +DLSYM_PROTO(pcre2_serialize_encode_8); +DLSYM_PROTO(pcre2_serialize_free_8); +DLSYM_PROTO(pcre2_serialize_get_number_of_codes_8); +#undef DLSYM_PROTO + +#undef pcre2_code_free +#define pcre2_code_free sym_pcre2_code_free_8 +#undef pcre2_compile +#define pcre2_compile sym_pcre2_compile_8 +#undef pcre2_config +#define pcre2_config sym_pcre2_config_8 +#undef pcre2_get_error_message +#define pcre2_get_error_message sym_pcre2_get_error_message_8 +#undef pcre2_match +#define pcre2_match sym_pcre2_match_8 +#undef pcre2_match_data_create_from_pattern +#define pcre2_match_data_create_from_pattern sym_pcre2_match_data_create_from_pattern_8 +#undef pcre2_match_data_free +#define pcre2_match_data_free sym_pcre2_match_data_free_8 +#undef pcre2_pattern_info +#define pcre2_pattern_info sym_pcre2_pattern_info_8 +#undef pcre2_serialize_decode +#define pcre2_serialize_decode sym_pcre2_serialize_decode_8 +#undef pcre2_serialize_encode +#define pcre2_serialize_encode sym_pcre2_serialize_encode_8 +#undef pcre2_serialize_free +#define pcre2_serialize_free sym_pcre2_serialize_free_8 +#undef pcre2_serialize_get_number_of_codes +#define pcre2_serialize_get_number_of_codes sym_pcre2_serialize_get_number_of_codes_8 + +#else + +static inline int regex_pcre2_load(void) +{ + return 0; +} + +#endif /* USE_PCRE2_DLSYM */ + +#endif /* USE_PCRE2 */ +#endif /* LIBSELINUX_REGEX_DLSYM_H */