-
Notifications
You must be signed in to change notification settings - Fork 11
perf: fix JNI/CGo correctness issues and reduce allocation overhead #83
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
a993251
3e737f1
9c1fb8e
bf91e7c
4336cd1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -184,11 +184,13 @@ int coraza_set_debug_log_callback(coraza_waf_config_t cfg, PyObject *cb) { | |||||||||||||||||||||
| %typemap(jstype) (const unsigned char *data, int length) "byte[]" | ||||||||||||||||||||||
| %typemap(javain) (const unsigned char *data, int length) "$javainput" | ||||||||||||||||||||||
| %typemap(in) (const unsigned char *data, int length) { | ||||||||||||||||||||||
| $1 = (unsigned char *)JCALL2(GetByteArrayElements, jenv, $input, NULL); | ||||||||||||||||||||||
| /* GetArrayLength must be called before GetPrimitiveArrayCritical: no other | ||||||||||||||||||||||
| * JNI calls are permitted while a critical section is open. */ | ||||||||||||||||||||||
| $2 = (int)JCALL1(GetArrayLength, jenv, $input); | ||||||||||||||||||||||
| $1 = (unsigned char *)JCALL2(GetPrimitiveArrayCritical, jenv, $input, NULL); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| %typemap(argout) (const unsigned char *data, int length) { | ||||||||||||||||||||||
| JCALL3(ReleaseByteArrayElements, jenv, $input, (jbyte *)$1, JNI_ABORT); | ||||||||||||||||||||||
| JCALL3(ReleasePrimitiveArrayCritical, jenv, $input, (jbyte *)$1, JNI_ABORT); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /* | ||||||||||||||||||||||
|
|
@@ -212,28 +214,59 @@ typedef struct { | |||||||||||||||||||||
| jmethodID mid; | ||||||||||||||||||||||
| } _swig_java_cb_ctx_t; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /* | ||||||||||||||||||||||
| * Obtain a JNIEnv for the current thread, attaching it as a daemon if needed. | ||||||||||||||||||||||
| * | ||||||||||||||||||||||
| * Attachment is cached per OS thread via __thread storage so repeated calls on | ||||||||||||||||||||||
| * the same thread skip the GetEnv round-trip. Attached threads are detached | ||||||||||||||||||||||
| * automatically by _swig_jni_detach_thread, which is registered as a | ||||||||||||||||||||||
| * pthread destructor on first attachment and fires when the OS thread exits. | ||||||||||||||||||||||
| * | ||||||||||||||||||||||
| * Returns NULL if attachment fails (JVM overloaded or shutting down); callers | ||||||||||||||||||||||
| * must check before dereferencing env. | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| #include <pthread.h> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| static pthread_key_t _swig_jni_tls_key; | ||||||||||||||||||||||
| static pthread_once_t _swig_jni_tls_once = PTHREAD_ONCE_INIT; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| static void _swig_jni_detach_thread(void *jvm_ptr) { | ||||||||||||||||||||||
| if (jvm_ptr) | ||||||||||||||||||||||
| ((JavaVM *)jvm_ptr)->DetachCurrentThread((JavaVM *)jvm_ptr); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
Comment on lines
+233
to
+236
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, locate the coraza.i file
find . -name "coraza.i" -type fRepository: corazawaf/libcoraza Length of output: 73 🏁 Script executed: # Read the coraza.i file around lines 233-236
sed -n '230,250p' coraza.iRepository: corazawaf/libcoraza Length of output: 797 🏁 Script executed: # Search for other JNI invocation patterns to understand the code style
rg 'DetachCurrentThread|AttachCurrentThread' -B 2 -A 2 coraza.iRepository: corazawaf/libcoraza Length of output: 582 Use the C JNI invocation form in Line 235 uses C++ member-access syntax ( Proposed fix static void _swig_jni_detach_thread(void *jvm_ptr) {
- if (jvm_ptr)
- ((JavaVM *)jvm_ptr)->DetachCurrentThread((JavaVM *)jvm_ptr);
+ if (jvm_ptr) {
+ JavaVM *jvm = (JavaVM *)jvm_ptr;
+ (*jvm)->DetachCurrentThread(jvm);
+ }
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| static void _swig_jni_tls_init(void) { | ||||||||||||||||||||||
| pthread_key_create(&_swig_jni_tls_key, _swig_jni_detach_thread); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| static JNIEnv *_swig_ensure_jni_env(JavaVM *jvm) { | ||||||||||||||||||||||
| JNIEnv *env = NULL; | ||||||||||||||||||||||
| jint rc = (*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_6); | ||||||||||||||||||||||
| if (rc == JNI_OK) | ||||||||||||||||||||||
| return env; | ||||||||||||||||||||||
| if (rc != JNI_EDETACHED) | ||||||||||||||||||||||
| return NULL; /* JVM error — caller must handle NULL */ | ||||||||||||||||||||||
| if ((*jvm)->AttachCurrentThreadAsDaemon(jvm, (void **)&env, NULL) != JNI_OK) | ||||||||||||||||||||||
| return NULL; | ||||||||||||||||||||||
| /* Register DetachCurrentThread to run when this OS thread exits. */ | ||||||||||||||||||||||
| pthread_once(&_swig_jni_tls_once, _swig_jni_tls_init); | ||||||||||||||||||||||
| pthread_setspecific(_swig_jni_tls_key, jvm); | ||||||||||||||||||||||
| return env; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| static void _swig_java_error_trampoline(void *ctx, coraza_matched_rule_t rule) { | ||||||||||||||||||||||
| _swig_java_cb_ctx_t *jctx = (_swig_java_cb_ctx_t *)ctx; | ||||||||||||||||||||||
| JNIEnv *env = NULL; | ||||||||||||||||||||||
| jboolean attached = JNI_FALSE; | ||||||||||||||||||||||
| if ((*jctx->jvm)->GetEnv(jctx->jvm, (void **)&env, JNI_VERSION_1_6) == JNI_EDETACHED) { | ||||||||||||||||||||||
| (*jctx->jvm)->AttachCurrentThreadAsDaemon(jctx->jvm, (void **)&env, NULL); | ||||||||||||||||||||||
| attached = JNI_TRUE; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| JNIEnv *env = _swig_ensure_jni_env(jctx->jvm); | ||||||||||||||||||||||
| if (!env) return; | ||||||||||||||||||||||
| (*env)->CallVoidMethod(env, jctx->obj, jctx->mid, (jlong)(uintptr_t)rule); | ||||||||||||||||||||||
| if ((*env)->ExceptionCheck(env)) (*env)->ExceptionDescribe(env); | ||||||||||||||||||||||
| if (attached) (*jctx->jvm)->DetachCurrentThread(jctx->jvm); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| static void _swig_java_debug_trampoline(void *ctx, coraza_debug_log_level_t level, | ||||||||||||||||||||||
| const char *msg, const char *fields) { | ||||||||||||||||||||||
| _swig_java_cb_ctx_t *jctx = (_swig_java_cb_ctx_t *)ctx; | ||||||||||||||||||||||
| JNIEnv *env = NULL; | ||||||||||||||||||||||
| jboolean attached = JNI_FALSE; | ||||||||||||||||||||||
| if ((*jctx->jvm)->GetEnv(jctx->jvm, (void **)&env, JNI_VERSION_1_6) == JNI_EDETACHED) { | ||||||||||||||||||||||
| (*jctx->jvm)->AttachCurrentThreadAsDaemon(jctx->jvm, (void **)&env, NULL); | ||||||||||||||||||||||
| attached = JNI_TRUE; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| JNIEnv *env = _swig_ensure_jni_env(jctx->jvm); | ||||||||||||||||||||||
| if (!env) return; | ||||||||||||||||||||||
| jstring jmsg = (*env)->NewStringUTF(env, msg ? msg : ""); | ||||||||||||||||||||||
| jstring jfields = (*env)->NewStringUTF(env, fields ? fields : ""); | ||||||||||||||||||||||
| /* NewStringUTF returns NULL on OOM; skip the call rather than crash. */ | ||||||||||||||||||||||
|
|
@@ -243,7 +276,6 @@ static void _swig_java_debug_trampoline(void *ctx, coraza_debug_log_level_t leve | |||||||||||||||||||||
| if (jmsg) (*env)->DeleteLocalRef(env, jmsg); | ||||||||||||||||||||||
| if (jfields) (*env)->DeleteLocalRef(env, jfields); | ||||||||||||||||||||||
| if ((*env)->ExceptionCheck(env)) (*env)->ExceptionDescribe(env); | ||||||||||||||||||||||
| if (attached) (*jctx->jvm)->DetachCurrentThread(jctx->jvm); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| JNIEXPORT jint JNICALL Java_coraza_coraza_1set_1error_1callback( | ||||||||||||||||||||||
|
|
@@ -353,7 +385,25 @@ typedef enum coraza_severity_t { | |||||||||||||||||||||
|
|
||||||||||||||||||||||
| /* | ||||||||||||||||||||||
| * Function declarations | ||||||||||||||||||||||
| * | ||||||||||||||||||||||
| * Release the Python GIL for calls that cross into Go and do real work, so | ||||||||||||||||||||||
| * other Python threads can run concurrently. coraza_append_request_body and | ||||||||||||||||||||||
| * coraza_append_response_body are intentionally excluded: the Python typemap | ||||||||||||||||||||||
| * extracts a raw pointer via PyBytes_AS_STRING / PyByteArray_AS_STRING and | ||||||||||||||||||||||
| * that pointer must remain valid for the duration of the call — releasing the | ||||||||||||||||||||||
| * GIL would allow another thread to resize or deallocate the buffer. | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| #ifdef SWIGPYTHON | ||||||||||||||||||||||
| %thread coraza_new_waf; | ||||||||||||||||||||||
| %thread coraza_rules_merge; | ||||||||||||||||||||||
| %thread coraza_process_connection; | ||||||||||||||||||||||
| %thread coraza_process_uri; | ||||||||||||||||||||||
| %thread coraza_process_request_headers; | ||||||||||||||||||||||
| %thread coraza_process_request_body; | ||||||||||||||||||||||
| %thread coraza_process_response_headers; | ||||||||||||||||||||||
| %thread coraza_process_response_body; | ||||||||||||||||||||||
| %thread coraza_process_logging; | ||||||||||||||||||||||
| #endif | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| extern coraza_waf_config_t coraza_new_waf_config(); | ||||||||||||||||||||||
| extern int coraza_rules_add_file(coraza_waf_config_t c, const char *file); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: corazawaf/libcoraza
Length of output: 95
🏁 Script executed:
sed -n '180,200p' ./coraza.iRepository: corazawaf/libcoraza
Length of output: 1051
🏁 Script executed:
sed -n '295,305p' ./libcoraza/coraza.goRepository: corazawaf/libcoraza
Length of output: 509
🏁 Script executed:
sed -n '318,328p' ./libcoraza/coraza.goRepository: corazawaf/libcoraza
Length of output: 403
🏁 Script executed:
Repository: corazawaf/libcoraza
Length of output: 45
🏁 Script executed:
grep -n "GetPrimitiveArrayCritical" ./coraza.iRepository: corazawaf/libcoraza
Length of output: 231
🏁 Script executed:
sed -n '186,210p' ./coraza.iRepository: corazawaf/libcoraza
Length of output: 1130
🏁 Script executed:
sed -n '175,200p' ./coraza.iRepository: corazawaf/libcoraza
Length of output: 1137
🏁 Script executed:
rg -n "coraza_append_request_body|coraza_append_response_body" ./coraza.i -A 10Repository: corazawaf/libcoraza
Length of output: 1933
🏁 Script executed:
Repository: corazawaf/libcoraza
Length of output: 768
🏁 Script executed:
Repository: corazawaf/libcoraza
Length of output: 1851
Guard
GetPrimitiveArrayCriticalagainst NULL and prevent JNI callback re-entrancy during array pinning.Line 190 does not check if
GetPrimitiveArrayCriticalreturns NULL. In Go 1.20+,unsafe.Slice(nil, length)wherelength > 0panics; non-empty input will hit this path inlibcoraza/coraza.golines 299 and 322. Additionally, the array remains pinned from line 190 to theargoutrelease, and ifWriteRequestBodyorWriteResponseBodytrigger Java callbacks (error or debug logging), those callbacks invoke JNI while the critical section is open, violating JNI rules. Either check for NULL and fail fast, or use a copying approach to avoid holding the critical section across callbacks.