[SSSD] [PATCH] TESTS: Add test for mmap cache client crash

Lukas Slebodnik lslebodn at redhat.com
Tue Nov 25 13:13:03 UTC 2014


On (20/11/14 11:25), Lukas Slebodnik wrote:
>ehlo,
>
>I was not able to mock functions with linker flang -wrap when
>they was called indirectly by another function from dynamic library.
>(It works with dirrect invocation of function)
>
>mc_client_tests_LDFLAGS = \
>    -Wl,-wrap,sss_nss_check_header \
>    -Wl,-wrap,sss_open_cloexec \
>    $(NULL)
>
>This is a reason why I wrote mocked function in unit test
>e.g.
>__real_sss_open_cloexec
>__wrap_sss_open_cloexec
>sss_open_cloexec
>
>If you know better way how to mock functions I will be glad to change test.
>
>LS

Attached are patches which fix warning caused by change in structure
"struct sss_cli_mc_ctx"

src/tests/cmocka/test_mc_client.c:49:41: error: missing initializer for field ‘active_threads’ of ‘struct sss_cli_mc_ctx’ [-Werror=missing-field-initializers]
                                         0 };
                                         ^
In file included from ../sssd/src/tests/cmocka/test_mc_client.c:36:0:
src/sss_client/nss_mc.h:58:14: note: ‘active_threads’ declared here
     uint32_t active_threads; /* count of threads which use memory cache */
              ^
cc1: all warnings being treated as errors


-------------- next part --------------
>From 6b62b209c49348b13df0ffad24e49098b19f5edb Mon Sep 17 00:00:00 2001
From: Lukas Slebodnik <lslebodn at redhat.com>
Date: Thu, 18 Sep 2014 07:58:36 +0200
Subject: [PATCH 1/3] BUILD: Prepare version of libnss_sss.so for tests.

It is not good idea to link unit test with "libnss_sss.so"
due to warning

*** Warning: Linking the binary xyz against the loadable module
*** libnss_sss.so is not portable!
---
 Makefile.am              | 12 ++++++++++++
 src/tests/dlopen-tests.c |  1 +
 2 files changed, 13 insertions(+)

diff --git a/Makefile.am b/Makefile.am
index 53ace65b9a9647ffdaff0776d5a55d3e7393a38c..95f282814ce64a6440f48628cf63c754466a161c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1687,6 +1687,18 @@ TEST_MOCK_PROVIDER_OBJ = \
      src/tests/cmocka/common_mock_sdap.c \
      src/tests/cmocka/common_mock_sysdb_objects.c
 
+check_LTLIBRARIES += \
+    libnss_sss_tests.la \
+    $(NULL)
+
+libnss_sss_tests_la_SOURCES = \
+    $(libnss_sss_la_SOURCES)
+libnss_sss_tests_la_LIBADD = \
+    $(libnss_sss_la_LIBADD)
+libnss_sss_tests_la_LDFLAGS = \
+    -shared \
+    -rpath $(libdir)
+
 EXTRA_nss_srv_tests_DEPENDENCIES = \
      $(ldblib_LTLIBRARIES)
 nss_srv_tests_SOURCES = \
diff --git a/src/tests/dlopen-tests.c b/src/tests/dlopen-tests.c
index 7e56d652461155045023fddeb988f4f4ba017277..afe6de8982042458d4fcec2336dc2c96e69afe8f 100644
--- a/src/tests/dlopen-tests.c
+++ b/src/tests/dlopen-tests.c
@@ -43,6 +43,7 @@ struct so {
     { "libsss_idmap.so", { LIBPFX"libsss_idmap.so", NULL } },
     { "libsss_nss_idmap.so", { LIBPFX"libsss_nss_idmap.so", NULL } },
     { "libnss_sss.so", { LIBPFX"libnss_sss.so", NULL } },
+    { "libnss_sss_tests.so", { LIBPFX"libnss_sss_tests.so", NULL } },
     { "pam_sss.so", { LIBPFX"pam_sss.so", NULL } },
 #ifdef BUILD_LIBWBCLIENT
     { "libwbclient.so", { LIBPFX"libwbclient.so", NULL } },
-- 
2.1.0

-------------- next part --------------
>From dba96682473205f27bf17d9882c286d3ca0d984b Mon Sep 17 00:00:00 2001
From: Lukas Slebodnik <lslebodn at redhat.com>
Date: Tue, 25 Nov 2014 13:50:49 +0100
Subject: [PATCH 2/3] TESTS: Add missing dependency to  libsss_test_common

Compilation of another test failed due to missing functions.

  CCLD     mc_client_tests
./.libs/libsss_test_common.a(common_dom.o): In function `create_dom_test_ctx':
src/tests/common_dom.c:65: undefined reference to `confdb_init'
src/tests/common_dom.c:72: undefined reference to `confdb_add_param'
src/tests/common_dom.c:86: undefined reference to `confdb_add_param'
src/tests/common_dom.c:96: undefined reference to `confdb_add_param'
src/tests/common_dom.c:107: undefined reference to `sssd_domain_init'
src/tests/common_dom.c:116: undefined reference to `sss_names_init_from_args'
collect2: error: ld returned 1 exit status
Makefile:8379: recipe for target 'mc_client_tests' failed
---
 Makefile.am | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/Makefile.am b/Makefile.am
index 95f282814ce64a6440f48628cf63c754466a161c..0784c5457cf7580ddd4d3d48876c5d7a6f9f4705 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1229,7 +1229,9 @@ libsss_test_common_la_SOURCES = \
     src/tests/common.c
 libsss_test_common_la_LIBADD = \
     $(TALLOC_LIBS) \
-    $(TEVENT_LIBS)
+    $(TEVENT_LIBS) \
+    $(SSSD_INTERNAL_LTLIBS) \
+    $(NULL)
 
 if HAVE_CHECK
 libsss_test_common_la_SOURCES += \
-- 
2.1.0

-------------- next part --------------
>From 3f9353b0d561a0d6b8601a97360892a7f44e50cc Mon Sep 17 00:00:00 2001
From: Lukas Slebodnik <lslebodn at redhat.com>
Date: Tue, 23 Sep 2014 15:30:48 +0200
Subject: [PATCH 3/3] TESTS: Add test for mmap cache client crash

Test create race condition  between two threads when memory cache is
invalidated(SSS_MC_HEADER_RECYCLED).

Unit test for:
https://fedorahosted.org/sssd/ticket/2445
---
 Makefile.am                       |  17 ++
 src/tests/cmocka/test_mc_client.c | 343 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 360 insertions(+)
 create mode 100644 src/tests/cmocka/test_mc_client.c

diff --git a/Makefile.am b/Makefile.am
index 0784c5457cf7580ddd4d3d48876c5d7a6f9f4705..7b9da82e042081e1a34c399827069c1892b905ba 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -214,6 +214,7 @@ if HAVE_CMOCKA
         test_search_bases \
         sdap-tests \
         test_sysdb_views \
+        mc_client_tests \
         $(NULL)
 
 if BUILD_IFP
@@ -2036,6 +2037,22 @@ sdap_tests_LDADD = \
     libsss_test_common.la \
     $(NULL)
 
+mc_client_tests_SOURCES = \
+    src/tests/cmocka/test_mc_client.c \
+    $(NULL)
+mc_client_tests_CFLAGS = \
+    $(AM_CFLAGS) \
+    $(NULL)
+mc_client_tests_LDADD = \
+    $(CMOCKA_LIBS) \
+    $(POPT_LIBS) \
+    $(LIBADD_DL) \
+    libsss_debug.la \
+    -lpthread \
+    libnss_sss_tests.la \
+    libsss_test_common.la \
+    $(NULL)
+
 if BUILD_IFP
 ifp_tests_SOURCES = \
      $(TEST_MOCK_RESP_OBJ) \
diff --git a/src/tests/cmocka/test_mc_client.c b/src/tests/cmocka/test_mc_client.c
new file mode 100644
index 0000000000000000000000000000000000000000..0369a3ed618a3d69a887fa0b20a65df9b574df94
--- /dev/null
+++ b/src/tests/cmocka/test_mc_client.c
@@ -0,0 +1,343 @@
+/*
+    Authors:
+        Lukas Slebodnik <lslebodn at redhat.com>
+
+    Copyright (C) 2014 Red Hat
+
+    SSSD tests: Memory cache client Tests
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* we need to define extra feature macros for RTLD_NEXT */
+#include "config.h"
+
+#include <popt.h>
+#include <pthread.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <dlfcn.h>
+
+#include "util/io.h"
+#include "sss_client/nss_mc.h"
+#include "tests/cmocka/common_mock.h"
+
+#define TESTS_PATH TEST_DIR "/test_mc_client"
+const char *TEST_MC_CACHE = TESTS_PATH "/dummy";
+
+enum {
+    CALL_REAL = 0,
+    CALL_THREAD1,
+    CALL_THREAD2,
+};
+
+/* Barrier variable */
+pthread_barrier_t barr;
+
+static struct sss_cli_mc_ctx mc_ctx = { UNINITIALIZED, -1, 0, NULL, 0, NULL, 0,
+                                        NULL, 0, 0 };
+
+/*
+ ***************************************************************************
+ * Mocked functions                                                        *
+ ***************************************************************************
+ */
+
+int __real_sss_open_cloexec(const char *pathname, int flags, int *ret)
+{
+    typedef int (*sss_open_cloexec_ptr)(const char *, int, int *);
+
+    sss_open_cloexec_ptr real_fun
+        = (sss_open_cloexec_ptr)dlsym(RTLD_NEXT, "sss_open_cloexec");
+
+    assert_non_null(real_fun);
+    return real_fun(pathname, flags, ret);
+}
+
+int __wrap_sss_open_cloexec(const char *pathname, int flags, int *ret)
+{
+    const char *fake_path = sss_mock_ptr_type(const char *);
+
+    return __real_sss_open_cloexec(fake_path, flags, ret);
+}
+
+int sss_open_cloexec(const char *pathname, int flags, int *ret)
+{
+    return __wrap_sss_open_cloexec(pathname, flags, ret);
+}
+
+/* Intercept header checking of memory cache */
+errno_t sss_nss_check_header_thread1(struct sss_cli_mc_ctx *ctx);
+errno_t sss_nss_check_header_thread2(struct sss_cli_mc_ctx *ctx);
+
+errno_t __real_sss_nss_check_header(struct sss_cli_mc_ctx *ctx)
+{
+    typedef errno_t (*sss_nss_check_header_ptr)(struct sss_cli_mc_ctx * ctx);
+
+    sss_nss_check_header_ptr real_fun
+        = (sss_nss_check_header_ptr)dlsym(RTLD_NEXT, "sss_nss_check_header");
+
+    assert_non_null(real_fun);
+    return real_fun(ctx);
+}
+
+errno_t __wrap_sss_nss_check_header(struct sss_cli_mc_ctx *ctx)
+{
+    int id = sss_mock_type(int);
+    switch (id) {
+    case CALL_REAL:
+        /* execure real implementation */
+        return __real_sss_nss_check_header(ctx);
+        break;
+    case CALL_THREAD1:
+        return sss_nss_check_header_thread1(ctx);
+    case CALL_THREAD2:
+        return sss_nss_check_header_thread2(ctx);
+    default:
+        /* should not be called */
+        fail();
+        break;
+    }
+
+    return 0;
+}
+
+errno_t sss_nss_check_header(struct sss_cli_mc_ctx *ctx)
+{
+    return __wrap_sss_nss_check_header(ctx);
+}
+
+errno_t sss_nss_check_header_thread1(struct sss_cli_mc_ctx *ctx)
+{
+    errno_t ret;
+    int rc;
+
+    ret = __real_sss_nss_check_header(ctx);
+    rc = pthread_barrier_wait(&barr);
+    if (rc != 0 && rc != PTHREAD_BARRIER_SERIAL_THREAD) {
+        fail();
+    }
+
+    assert_int_equal(ret, EINVAL);
+
+    return ret;
+}
+
+errno_t sss_nss_check_header_thread2(struct sss_cli_mc_ctx *ctx)
+{
+    errno_t ret;
+    int rc = pthread_barrier_wait(&barr);
+    if (rc != 0 && rc != PTHREAD_BARRIER_SERIAL_THREAD) {
+        fail();
+    }
+
+    /*
+     * 1 seconds is exaggerated, but it's better to be sure.
+     * 1 second is enough time to unmap file, close file and clear context
+     * With real race condition, crash happened after 1st thread unmapped file
+     * and 2nd thread want to test header of mmaped file
+     */
+    sleep(1);
+    ret = __real_sss_nss_check_header(ctx);
+    assert_int_equal(ret, EINVAL);
+
+    return ret;
+}
+
+void *entry_point(void *arg)
+{
+    errno_t ret;
+    const errno_t expected_ret = (const errno_t)(intptr_t)arg;
+
+    ret = sss_nss_mc_get_ctx("str_will_be_mocked", &mc_ctx);
+    assert_int_equal(ret, expected_ret);
+
+    return NULL;
+}
+
+/*
+ ***************************************************************************
+ * Memory cache related functions                                          *
+ ***************************************************************************
+ */
+
+static void create_fake_cache(const char *file_name)
+{
+    int ret;
+    int fd;
+    ssize_t size;
+    struct sss_mc_header h;
+
+    ret = unlink(file_name);
+    if (ret != 0 && errno != ENOENT) {
+        fail();
+    }
+
+    fd = creat(file_name, S_IRUSR | S_IWUSR);
+    assert_return_code(fd, errno);
+
+    h.b1 = h.b2 = 0xf0000001;
+    h.major_vno = SSS_MC_MAJOR_VNO;
+    h.minor_vno = SSS_MC_MINOR_VNO;
+    h.status = SSS_MC_HEADER_ALIVE;
+    h.seed = time(NULL);
+    h.dt_size = 6000000;
+    h.ft_size = 6250;
+    h.ht_size = 400000;
+    h.data_table = MC_HEADER_SIZE;
+    h.free_table = h.data_table + MC_ALIGN64(h.dt_size);
+    h.hash_table = h.free_table + MC_ALIGN64(h.ft_size);
+    h.reserved = 0;
+
+    size = write(fd, &h, sizeof(h));
+    assert_int_equal(size, sizeof(h));
+
+    ret = ftruncate(fd, h.hash_table + MC_ALIGN64(h.ht_size));
+    assert_return_code(ret, errno);
+}
+
+static void set_header_status(struct sss_cli_mc_ctx *ctx,
+                              const char *file_name, uint32_t status)
+{
+    int fd;
+    int ret;
+    const off_t STATUS_OFFSET = offsetof(struct sss_mc_header, status);
+
+    fd = open(file_name, O_RDWR);
+    assert_return_code(fd, errno);
+
+    ret = lseek(fd, STATUS_OFFSET, SEEK_SET);
+    assert_return_code(ret, errno);
+
+    ret = write(fd, &status, sizeof(status));
+    assert_return_code(ret, errno);
+
+    ret = close(fd);
+    assert_int_equal(ret, 0);
+}
+
+/*
+ ***************************************************************************
+ * Unit tests, setup and teardown functions                                *
+ ***************************************************************************
+ */
+
+static void test_mc_client_setup(void **state)
+{
+    create_fake_cache(TEST_MC_CACHE);
+    *state = discard_const_p(char, TEST_MC_CACHE);
+}
+
+static void test_mc_client_teardown(void **state)
+{
+    int ret = unlink((const char *)*state);
+    assert_return_code(ret, errno);
+}
+
+static void test_client_reinit_race_condition(void **state)
+{
+    errno_t ret;
+    const int THREADS_COUNT = 2;
+    int i;
+    const char *mc_cache_filename = (const char *)*state;
+    pthread_t thr[THREADS_COUNT];
+
+    create_fake_cache(mc_cache_filename);
+
+    will_return(__wrap_sss_open_cloexec, mc_cache_filename);
+    will_return(__wrap_sss_nss_check_header, CALL_REAL);
+
+    ret = sss_nss_mc_get_ctx("str_will_be_mocked", &mc_ctx);
+    assert_int_equal(ret, ERR_OK);
+
+    will_return(__wrap_sss_nss_check_header, CALL_REAL);
+    ret = sss_nss_check_header(&mc_ctx);
+    assert_int_equal(ret, ERR_OK);
+
+    set_header_status(&mc_ctx, mc_cache_filename, SSS_MC_HEADER_RECYCLED);
+
+    will_return(__wrap_sss_nss_check_header, CALL_REAL);
+    ret = sss_nss_check_header(&mc_ctx);
+    assert_int_equal(ret, EINVAL);
+
+    will_return(__wrap_sss_nss_check_header, CALL_THREAD1);
+    will_return(__wrap_sss_nss_check_header, CALL_THREAD2);
+
+    ret = pthread_barrier_init(&barr, NULL, THREADS_COUNT);
+    assert_int_equal(ret, 0);
+
+    for (i = 0; i < THREADS_COUNT; ++i) {
+        ret = pthread_create(&thr[i], NULL, &entry_point, (void *)EINVAL);
+        assert_int_equal(ret, 0);
+    }
+
+    /* cleanup */
+    for (i = 0; i < THREADS_COUNT; ++i) {
+        ret = pthread_join(thr[i], NULL);
+        assert_int_equal(ret, 0);
+    }
+
+    ret = pthread_barrier_destroy(&barr);
+    assert_int_equal(ret, 0);
+}
+
+int main(int argc, const char *argv[])
+{
+    int no_cleanup = 0;
+    poptContext pc;
+    int opt;
+    int ret;
+    struct poptOption long_options[] = {
+        SSSD_DEBUG_OPTS
+        {"no-cleanup", 'n', POPT_ARG_NONE, &no_cleanup, 0,
+         _("Do not delete the test database after a test run"), NULL },
+        POPT_TABLEEND
+    };
+    const UnitTest tests[] = {
+        unit_test_setup_teardown(test_client_reinit_race_condition,
+                                 test_mc_client_setup,
+                                 test_mc_client_teardown),
+    };
+
+    /* Set debug level to invalid value so we can deside if -d 0 was used. */
+    debug_level = SSSDBG_INVALID;
+
+    pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+    while ((opt = poptGetNextOpt(pc)) != -1) {
+        switch (opt) {
+        default:
+            fprintf(stderr, "\nInvalid option %s: %s\n\n",
+                    poptBadOption(pc, 0), poptStrerror(opt));
+            poptPrintUsage(pc, stderr, 0);
+            return 1;
+        }
+    }
+    poptFreeContext(pc);
+
+    DEBUG_CLI_INIT(debug_level);
+
+    /* Even though normally the tests should clean up after themselves
+     * they might not after a failed run. Remove the old db to be sure */
+    tests_set_cwd();
+    test_dom_suite_setup(TESTS_PATH);
+
+    ret = run_tests(tests);
+    if (ret == 0 && !no_cleanup) {
+        test_dom_suite_cleanup(TESTS_PATH, NULL, NULL);
+    }
+    return ret;
+}
-- 
2.1.0



More information about the sssd-devel mailing list