BUG/MAJOR: hlua: improper lock usage with hlua_ctx_resume()
hlua_ctx_resume() itself can safely be used as-is in a multithreading
context because it takes care of taking the lua lock.
However, when hlua_ctx_resume() returns, the lock is released and it is
thus the caller's responsibility to ensure it owns the lock prior to
performing additional manipulations on the Lua stack. Unfortunately, since
early haproxy lua implementation, we used to do it wrong:
The most common hlua_ctx_resume() pattern we can find in the code (because
it was duplicated over and over over time) is the following:
|ret = hlua_ctx_resume()
|switch (ret) {
| case HLUA_E_OK:
| break;
| case HLUA_E_ERRMSG:
| break;
| [...]
|}
Problem is: for some of the switch cases, we still perform lua stack
manipulations. This is the case for the HLUA_E_ERRMSG for instance where
we often use lua_tostring() to retrieve last lua error message on the top
of the stack, or sometimes for the HLUA_E_OK case, when we need to perform
some lua cleanup logic once the resume ended. But all of this is done
WITHOUT the lua lock, so this means that the main lua stack could be
accessed simultaneously by concurrent threads when a script was loaded
using 'lua-load'.
While it is not critical for switch-cases dedicated to error handling,
(those are not supposed to happen very often), it can be very problematic
for stack manipulations occuring in the HLUA_E_OK case under heavy load
for instance. In this case, main lua stack corruptions will eventually
happen. This is especially true inside hlua_filter_new(), where this bug
was known to cause lua stack corruptions under load, leading to lua errors
and even crashing the process as reported by @bgrooot in GH #2467.
The fix is relatively simple, once hlua_ctx_resume() returns: we should
consider that ANY lua stack access should be lua-lock protected. If the
related lua calls may raise lua errors, then (RE)SET_SAFE_LJMP
combination should be used as usual (it allows to lock the lua stack and
catch lua exceptions at the same time), else hlua_{lock,unlock} may be
used if no exceptions are expected.
This patch should fix GH #2467.
It should be backported to all stable versions.
[ada: some ctx adj will be required for older versions as event_hdl
doesn't exist prior to 2.8 and filters were implemented in 2.5, thus
some chunks won't apply]
(cherry picked from commit 8670db7a898308455bf02315710c1204d39e4664)
Signed-off-by: Christopher Faulet <cfaulet@haproxy.com>
(cherry picked from commit b0ff26d52bdf53f924800927ea32382bbd40924d)
Signed-off-by: Christopher Faulet <cfaulet@haproxy.com>
(cherry picked from commit 258fc3fa01157e12969186a9b57ab9ab2491eb13)
Signed-off-by: Christopher Faulet <cfaulet@haproxy.com>
(cherry picked from commit 9fa313713b99aa5018071d83ec665e38d2bd0c1a)
Signed-off-by: Christopher Faulet <cfaulet@haproxy.com>
(cherry picked from commit e41a1c7fc1189cc16d3e32d1a6d782757eb27f97)
Signed-off-by: Christopher Faulet <cfaulet@haproxy.com>
diff --git a/src/hlua.c b/src/hlua.c
index c01b084..fa49e9a 100644
--- a/src/hlua.c
+++ b/src/hlua.c
@@ -6477,10 +6477,12 @@
/* finished with error. */
case HLUA_E_ERRMSG:
+ hlua_lock(hlua);
SEND_ERR(NULL, "Lua task: %s.\n", hlua_tostring_safe(hlua->T, -1));
hlua_ctx_destroy(hlua);
task_destroy(task);
task = NULL;
+ hlua_unlock(hlua);
break;
case HLUA_E_ERR:
default:
@@ -6714,9 +6716,12 @@
switch (hlua_ctx_resume(stream->hlua, 0)) {
/* finished. */
case HLUA_E_OK:
+ hlua_lock(stream->hlua);
/* If the stack is empty, the function fails. */
- if (lua_gettop(stream->hlua->T) <= 0)
+ if (lua_gettop(stream->hlua->T) <= 0) {
+ hlua_unlock(stream->hlua);
return 0;
+ }
/* Convert the returned value in sample. */
hlua_lua2smp(stream->hlua->T, -1, smp);
@@ -6725,6 +6730,7 @@
*/
smp_dup(smp);
lua_pop(stream->hlua->T, 1);
+ hlua_unlock(stream->hlua);
return 1;
/* yield. */
@@ -6735,9 +6741,11 @@
/* finished with error. */
case HLUA_E_ERRMSG:
/* Display log. */
+ hlua_lock(stream->hlua);
SEND_ERR(stream->be, "Lua converter '%s': %s.\n",
fcn->name, hlua_tostring_safe(stream->hlua->T, -1));
lua_pop(stream->hlua->T, 1);
+ hlua_unlock(stream->hlua);
return 0;
case HLUA_E_ETMOUT:
@@ -6839,9 +6847,12 @@
switch (hlua_ctx_resume(stream->hlua, 0)) {
/* finished. */
case HLUA_E_OK:
+ hlua_lock(stream->hlua);
/* If the stack is empty, the function fails. */
- if (lua_gettop(stream->hlua->T) <= 0)
+ if (lua_gettop(stream->hlua->T) <= 0) {
+ hlua_unlock(stream->hlua);
return 0;
+ }
/* Convert the returned value in sample. */
hlua_lua2smp(stream->hlua->T, -1, smp);
@@ -6850,6 +6861,7 @@
*/
smp_dup(smp);
lua_pop(stream->hlua->T, 1);
+ hlua_unlock(stream->hlua);
/* Set the end of execution flag. */
smp->flags &= ~SMP_F_MAY_CHANGE;
@@ -6863,9 +6875,11 @@
/* finished with error. */
case HLUA_E_ERRMSG:
/* Display log. */
+ hlua_lock(stream->hlua);
SEND_ERR(smp->px, "Lua sample-fetch '%s': %s.\n",
fcn->name, hlua_tostring_safe(stream->hlua->T, -1));
lua_pop(stream->hlua->T, 1);
+ hlua_unlock(stream->hlua);
return 0;
case HLUA_E_ETMOUT:
@@ -7171,8 +7185,10 @@
/* finished. */
case HLUA_E_OK:
/* Catch the return value */
+ hlua_lock(s->hlua);
if (lua_gettop(s->hlua->T) > 0)
act_ret = lua_tointeger(s->hlua->T, -1);
+ hlua_unlock(s->hlua);
/* Set timeout in the required channel. */
if (act_ret == ACT_RET_YIELD) {
@@ -7212,9 +7228,11 @@
/* finished with error. */
case HLUA_E_ERRMSG:
/* Display log. */
+ hlua_lock(s->hlua);
SEND_ERR(px, "Lua function '%s': %s.\n",
rule->arg.hlua_rule->fcn->name, hlua_tostring_safe(s->hlua->T, -1));
lua_pop(s->hlua->T, 1);
+ hlua_unlock(s->hlua);
goto end;
case HLUA_E_ETMOUT:
@@ -7395,9 +7413,11 @@
/* finished with error. */
case HLUA_E_ERRMSG:
/* Display log. */
+ hlua_lock(hlua);
SEND_ERR(px, "Lua applet tcp '%s': %s.\n",
rule->arg.hlua_rule->fcn->name, hlua_tostring_safe(hlua->T, -1));
lua_pop(hlua->T, 1);
+ hlua_unlock(hlua);
goto error;
case HLUA_E_ETMOUT:
@@ -7602,9 +7622,11 @@
/* finished with error. */
case HLUA_E_ERRMSG:
/* Display log. */
+ hlua_lock(hlua);
SEND_ERR(px, "Lua applet http '%s': %s.\n",
rule->arg.hlua_rule->fcn->name, hlua_tostring_safe(hlua->T, -1));
lua_pop(hlua->T, 1);
+ hlua_unlock(hlua);
goto error;
case HLUA_E_ETMOUT:
@@ -8221,9 +8243,11 @@
/* finished with error. */
case HLUA_E_ERRMSG:
/* Display log. */
+ hlua_lock(hlua);
SEND_ERR(NULL, "Lua cli '%s': %s.\n",
fcn->name, hlua_tostring_safe(hlua->T, -1));
lua_pop(hlua->T, 1);
+ hlua_unlock(hlua);
return 1;
case HLUA_E_ETMOUT: