From 4dbfbc94c20126134ea74f0f40fdc8c54796bb2c Mon Sep 17 00:00:00 2001 From: Tomas Halman Date: Thu, 9 Mar 2023 15:05:07 +0100 Subject: [PATCH] Fix jq segfault when used in threads Resolves: rhbz#2176542 --- 0003-fix-pthread-segfault.patch | 244 ++++++++++++++++++++++++++++++++ jq.spec | 7 +- 2 files changed, 250 insertions(+), 1 deletion(-) create mode 100644 0003-fix-pthread-segfault.patch diff --git a/0003-fix-pthread-segfault.patch b/0003-fix-pthread-segfault.patch new file mode 100644 index 0000000..5082d46 --- /dev/null +++ b/0003-fix-pthread-segfault.patch @@ -0,0 +1,244 @@ +commit fead9669cb67badb22789d3ed1888779ed85c679 +Author: Tomas Halman +Date: Thu Mar 9 11:34:44 2023 +0100 + + Test that jq works in threads + + This patch implements test that searches a key in simple + json in pthread. + +diff -up jq-1.6/src/jq_test.c.orig jq-1.6/src/jq_test.c +--- jq-1.6/src/jq_test.c.orig 2023-03-09 14:02:16.980062057 +0100 ++++ jq-1.6/src/jq_test.c 2023-03-09 14:03:38.347017032 +0100 +@@ -2,12 +2,17 @@ + #include + #include + #include ++#ifdef HAVE_PTHREAD ++#include ++#endif + #include "jv.h" + #include "jq.h" + + static void jv_test(); + static void run_jq_tests(jv, int, FILE *, int, int); +- ++#ifdef HAVE_PTHREAD ++static void run_jq_pthread_tests(); ++#endif + + int jq_testsuite(jv libdirs, int verbose, int argc, char* argv[]) { + FILE *testdata = stdin; +@@ -32,6 +37,9 @@ int jq_testsuite(jv libdirs, int verbose + } + } + run_jq_tests(libdirs, verbose, testdata, skip, take); ++#ifdef HAVE_PTHREAD ++ run_jq_pthread_tests(); ++#endif + return 0; + } + +@@ -105,7 +113,7 @@ static void run_jq_tests(jv lib_dirs, in + if (buf[0] == '\n' || (buf[0] == '\r' && buf[1] == '\n')) + break; + } +- ++ + must_fail = 0; + check_msg = 0; + +@@ -229,7 +237,7 @@ static void run_jq_tests(jv lib_dirs, in + total_skipped = tests_to_skip - skip; + } + +- printf("%d of %d tests passed (%d malformed, %d skipped)\n", ++ printf("%d of %d tests passed (%d malformed, %d skipped)\n", + passed, tests, invalid, total_skipped); + + if (skip > 0) { +@@ -241,6 +249,88 @@ static void run_jq_tests(jv lib_dirs, in + } + + ++/// pthread regression test ++#ifdef HAVE_PTHREAD ++#define NUMBER_OF_THREADS 3 ++struct test_pthread_data { ++ int result; ++}; ++ ++static int test_pthread_jq_parse(jq_state *jq, struct jv_parser *parser) ++{ ++ int rv = 0; ++ jv value; ++ ++ value = jv_parser_next(parser); ++ while (jv_is_valid(value)) { ++ jq_start(jq, value, 0); ++ jv result = jq_next(jq); ++ ++ while (jv_is_valid(result)) { ++ jv_free(result); ++ result = jq_next(jq); ++ } ++ jv_free(result); ++ value = jv_parser_next(parser); ++ } ++ jv_free(value); ++ return rv; ++} ++ ++static void *test_pthread_run(void *ptr) { ++ int rv; ++ jq_state *jq; ++ const char *prg = ".data"; ++ const char *buf = "{ \"data\": 1 }"; ++ struct test_pthread_data *data = ptr; ++ ++ jq = jq_init(); ++ if (jq_compile(jq, prg) == 0) { ++ jq_teardown(&jq); ++ return NULL; ++ } ++ ++ struct jv_parser *parser = jv_parser_new(0); ++ jv_parser_set_buf(parser, buf, strlen(buf), 0); ++ rv = test_pthread_jq_parse(jq, parser); ++ ++ data->result = rv; ++ ++ jv_parser_free(parser); ++ jq_teardown(&jq); ++ return NULL; ++} ++ ++static void run_jq_pthread_tests() { ++ pthread_t threads[NUMBER_OF_THREADS]; ++ struct test_pthread_data data[NUMBER_OF_THREADS]; ++ int createerror; ++ int a; ++ ++ memset(&threads, 0, sizeof(threads)); ++ memset(&data, 0, sizeof(data)); ++ ++ // Create all threads ++ for (a = 0; a < NUMBER_OF_THREADS; ++a) { ++ createerror = pthread_create(&threads[a], NULL, test_pthread_run, &data[a]); ++ assert(createerror == 0); ++ } ++ ++ // wait for all threads ++ for(a = 0; a < NUMBER_OF_THREADS; ++a) { ++ if (threads[a] != 0) { ++ pthread_join(threads[a], NULL); ++ } ++ } ++ ++ // check results ++ for(a = 0; a < NUMBER_OF_THREADS; ++a) { ++ assert(data[a].result == 0); ++ } ++} ++#endif // HAVE_PTHREAD ++ ++ + static void jv_test() { + /// JSON parser regression tests + { + +commit f05c5bb521f404592b137e02a1b8abd50da87ca3 +Author: Tomas Halman +Date: Wed Mar 1 20:07:28 2023 +0100 + + Fix segmentation fault when using jq in threads + + In previous code nomem_handler in jv_alloc.c is called only once + and therefore the structure is not initialized for second and + following threads. + + This leads to segmentation fault in multi-threading environment. + + This patch moves initialization of nomem_handler out of the + pthread_once call. + +diff -up jq-1.6/src/jv_alloc.c.orig jq-1.6/src/jv_alloc.c +--- jq-1.6/src/jv_alloc.c.orig 2018-11-02 02:49:29.000000000 +0100 ++++ jq-1.6/src/jv_alloc.c 2023-03-09 14:13:47.810177037 +0100 +@@ -45,9 +45,11 @@ static void memory_exhausted() { + #ifdef HAVE_PTHREAD_KEY_CREATE + #include + +-pthread_key_t nomem_handler_key; +-pthread_once_t mem_once = PTHREAD_ONCE_INIT; ++static pthread_key_t nomem_handler_key; ++static pthread_once_t mem_once = PTHREAD_ONCE_INIT; + ++/* tsd_fini is called on application exit */ ++/* it clears the nomem_handler allocated in the main thread */ + static void tsd_fini(void) { + struct nomem_handler *nomem_handler; + nomem_handler = pthread_getspecific(nomem_handler_key); +@@ -57,30 +59,45 @@ static void tsd_fini(void) { + } + } + ++/* The tsd_fini_thread is a destructor set by calling */ ++/* pthread_key_create(&nomem_handler_key, tsd_fini_thread) */ ++/* It is called when thread ends */ ++static void tsd_fini_thread(void *nomem_handler) { ++ free(nomem_handler); ++} ++ + static void tsd_init(void) { +- if (pthread_key_create(&nomem_handler_key, NULL) != 0) { +- fprintf(stderr, "error: cannot create thread specific key"); ++ if (pthread_key_create(&nomem_handler_key, tsd_fini_thread) != 0) { ++ fprintf(stderr, "jq: error: cannot create thread specific key"); + abort(); + } + if (atexit(tsd_fini) != 0) { +- fprintf(stderr, "error: cannot set an exit handler"); ++ fprintf(stderr, "jq: error: cannot set an exit handler"); + abort(); + } +- struct nomem_handler *nomem_handler = calloc(1, sizeof(struct nomem_handler)); +- if (pthread_setspecific(nomem_handler_key, nomem_handler) != 0) { +- fprintf(stderr, "error: cannot set thread specific data"); +- abort(); ++} ++ ++static void tsd_init_nomem_handler(void) ++{ ++ if (pthread_getspecific(nomem_handler_key) == NULL) { ++ struct nomem_handler *nomem_handler = calloc(1, sizeof(struct nomem_handler)); ++ if (pthread_setspecific(nomem_handler_key, nomem_handler) != 0) { ++ fprintf(stderr, "jq: error: cannot set thread specific data"); ++ abort(); ++ } + } + } + + void jv_nomem_handler(jv_nomem_handler_f handler, void *data) { + pthread_once(&mem_once, tsd_init); // cannot fail ++ tsd_init_nomem_handler(); ++ + struct nomem_handler *nomem_handler; + + nomem_handler = pthread_getspecific(nomem_handler_key); + if (nomem_handler == NULL) { + handler(data); +- fprintf(stderr, "error: cannot allocate memory\n"); ++ fprintf(stderr, "jq: error: cannot allocate memory\n"); + abort(); + } + nomem_handler->handler = handler; +@@ -91,6 +108,8 @@ static void memory_exhausted() { + struct nomem_handler *nomem_handler; + + pthread_once(&mem_once, tsd_init); ++ tsd_init_nomem_handler(); ++ + nomem_handler = pthread_getspecific(nomem_handler_key); + if (nomem_handler) + nomem_handler->handler(nomem_handler->data); // Maybe handler() will longjmp() to safety diff --git a/jq.spec b/jq.spec index 0efcaa6..4068663 100644 --- a/jq.spec +++ b/jq.spec @@ -1,6 +1,6 @@ Name: jq Version: 1.6 -Release: 14%{?dist} +Release: 15%{?dist} Summary: Command-line JSON processor License: MIT and ASL 2.0 and CC-BY and GPLv3 @@ -10,6 +10,7 @@ Source0: https://github.com/stedolan/jq/releases/download/%{name}-%{versi Patch0: jq-decimal-literal-number.patch Patch1: 0001-iterration-problem-for-non-decimal-string.patch Patch2: 0002-add-mantest.patch +Patch3: 0003-fix-pthread-segfault.patch BuildRequires: gcc BuildRequires: flex @@ -100,6 +101,10 @@ make check %changelog +* Thu Mar 9 2023 Tomas Halman - 1.6-15 +- jq segfault when used in threads +- Resolves: rhbz#2176542 + * Fri Nov 4 2022 Tomas Halman - 1.6-6 - Add mantest to the gating - Related: rhbz#2049594