From b8530c6621de218d3ac73f7a5173233f38571dfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= Date: Thu, 4 Apr 2024 17:59:38 +0200 Subject: [PATCH 1/4] libselinux: constify regex interfaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Declare the read-only arguments for comparison and writing const. Signed-off-by: Christian Göttsche --- libselinux/src/regex.c | 12 ++++++------ libselinux/src/regex.h | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/libselinux/src/regex.c b/libselinux/src/regex.c index 88d82fedb2..56694d1eef 100644 --- a/libselinux/src/regex.c +++ b/libselinux/src/regex.c @@ -164,7 +164,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; @@ -269,7 +269,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; @@ -407,7 +407,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 +419,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 +510,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; diff --git a/libselinux/src/regex.h b/libselinux/src/regex.h index 2dfa253420..d449b1e700 100644 --- a/libselinux/src/regex.h +++ b/libselinux/src/regex.h @@ -120,7 +120,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 +149,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. From 8647894beeac974b84d54469435378b729b8e315 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= Date: Thu, 4 Apr 2024 17:59:48 +0200 Subject: [PATCH 2/4] libselinux: hide regex_data_create() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The function regex_data_create() is only used inside of regex.c, thus do not expose it in the header file regex.h. Signed-off-by: Christian Göttsche --- libselinux/src/regex.c | 10 ++++++++-- libselinux/src/regex.h | 5 ----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/libselinux/src/regex.c b/libselinux/src/regex.c index 56694d1eef..69d40044c8 100644 --- a/libselinux/src/regex.c +++ b/libselinux/src/regex.c @@ -29,6 +29,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) { @@ -283,7 +289,7 @@ int regex_cmp(const struct regex_data *regex1, const 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)); @@ -524,7 +530,7 @@ int regex_cmp(const struct regex_data *regex1, const 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)); } diff --git a/libselinux/src/regex.h b/libselinux/src/regex.h index d449b1e700..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 From ca15903591bab6aed27bd8e5cf66884eabd2497d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= Date: Thu, 4 Apr 2024 18:15:34 +0200 Subject: [PATCH 3/4] github: bump python and ruby versions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bump the maximum python version to 3.12 and the maximum ruby version to 3.3 in the GitHub CI. Also bump the setup-python action to v5. Signed-off-by: Christian Göttsche --- .github/workflows/run_tests.yml | 41 ++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 3bd859a378..627817a3b1 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -10,36 +10,39 @@ 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'} + - {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'} # 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.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 +70,7 @@ jobs: swig \ xmlto - pip install flake8 + pip install flake8 setuptools - name: Install Clang if: ${{ matrix.compiler == 'clang' }} From 8731a53ab126d6c83847df0ae490faaba1493b2c 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 4/4] 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 627817a3b1..e1f57362a9 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 */