From bb06806aab55ca3bdb903b20182686796cebb7c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= Date: Thu, 4 Apr 2024 18:00:09 +0200 Subject: [PATCH] libselinux: add build time option to drop hard dependency on libpcre2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently libselinux links to libpcre2. The regex library is used in the file backend of the selabel database for file context path matching. Some client applications using libselinux might not use that selabel functionality, but they still require to load libpcre2. Examples are dbus-broker and sshd (where openssh only uses the selabel interfaces to create ~/.ssh with the default context). Add a build time option, USE_PCRE2_DLSYM, to drop the hard dependency on libpcre2 and only load it, if actually needed, at runtime via dlopen(3). Since loading the database for the file backend takes a couple of milliseconds performance is not a concern. Signed-off-by: Christian Göttsche --- .github/workflows/run_tests.yml | 5 +- libselinux/Makefile | 10 ++- libselinux/src/regex.c | 34 +++++++++- libselinux/src/regex_dlsym.c | 110 ++++++++++++++++++++++++++++++++ libselinux/src/regex_dlsym.h | 65 +++++++++++++++++++ 5 files changed, 220 insertions(+), 4 deletions(-) create mode 100644 libselinux/src/regex_dlsym.c create mode 100644 libselinux/src/regex_dlsym.h diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 8ada9eaeae..6226f5ad1a 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -15,7 +15,8 @@ jobs: - {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'} - # Test several Python versions with the latest Ruby version + - {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'} @@ -88,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 69d40044c8..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" @@ -81,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; @@ -110,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; @@ -125,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; @@ -178,6 +191,9 @@ int regex_writef(const struct regex_data *regex, FILE *fp, int do_write_precompr 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, @@ -212,6 +228,9 @@ int regex_writef(const struct regex_data *regex, FILE *fp, int do_write_precompr void regex_data_free(struct regex_data *regex) { + if (regex_pcre2_load() < 0) + return; + if (regex) { if (regex->regex) pcre2_code_free(regex->regex); @@ -230,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 @@ -279,6 +302,10 @@ 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); @@ -546,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_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 */