Fix jq segfault when used in threads

Resolves: rhbz#2176542
This commit is contained in:
Tomas Halman 2023-03-09 15:05:07 +01:00
parent 1e15065d35
commit 4dbfbc94c2
2 changed files with 250 additions and 1 deletions

View File

@ -0,0 +1,244 @@
commit fead9669cb67badb22789d3ed1888779ed85c679
Author: Tomas Halman <thalman@redhat.com>
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 <stdio.h>
#include <string.h>
#include <stdlib.h>
+#ifdef HAVE_PTHREAD
+#include <pthread.h>
+#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 <thalman@redhat.com>
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.h>
-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

View File

@ -1,6 +1,6 @@
Name: jq Name: jq
Version: 1.6 Version: 1.6
Release: 14%{?dist} Release: 15%{?dist}
Summary: Command-line JSON processor Summary: Command-line JSON processor
License: MIT and ASL 2.0 and CC-BY and GPLv3 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 Patch0: jq-decimal-literal-number.patch
Patch1: 0001-iterration-problem-for-non-decimal-string.patch Patch1: 0001-iterration-problem-for-non-decimal-string.patch
Patch2: 0002-add-mantest.patch Patch2: 0002-add-mantest.patch
Patch3: 0003-fix-pthread-segfault.patch
BuildRequires: gcc BuildRequires: gcc
BuildRequires: flex BuildRequires: flex
@ -100,6 +101,10 @@ make check
%changelog %changelog
* Thu Mar 9 2023 Tomas Halman <thalman@redhat.com> - 1.6-15
- jq segfault when used in threads
- Resolves: rhbz#2176542
* Fri Nov 4 2022 Tomas Halman <thalman@redhat.com> - 1.6-6 * Fri Nov 4 2022 Tomas Halman <thalman@redhat.com> - 1.6-6
- Add mantest to the gating - Add mantest to the gating
- Related: rhbz#2049594 - Related: rhbz#2049594