BUG/MINOR: hlua: don't use lua_tostring() from unprotected contexts

As per lua documentation, lua_tostring() may raise a memory error.
However, we're often using it to fetch the error message at the top of
the stack (ie: after a failing lua call) from unprotected environments.
In practise, lua_tostring() has rare chances of failing, but still, if
it happens to be the case, it could crash the process and we better not
risk it.

So here, we add hlua_tostring_safe() function, which works exactly as
lua_tostring(), but the function cannot LJMP as it will catch
lua_tostring() exceptions to return NULL instead.

Everywhere lua_tostring() was used to retrieve error string from such
unprotected contexts, we now rely on hlua_tostring_safe().

This should be backported to all stable versions.

[ada: ctx adj will be required, for versions prior to 2.8 event_hdl
 API didn't exist so some chunks won't apply, and prior to 2.5 filters
 API didn't exist either, so again, some chunks should be ignored]

(cherry picked from commit 369bfa0b50396a496a03fad59844196a3287784e)
Signed-off-by: Christopher Faulet <cfaulet@haproxy.com>
(cherry picked from commit 14f472a5fc5e3f0944957f4d27b1311cdf05e8c2)
Signed-off-by: Christopher Faulet <cfaulet@haproxy.com>
diff --git a/src/hlua.c b/src/hlua.c
index fcbde24..329ffab 100644
--- a/src/hlua.c
+++ b/src/hlua.c
@@ -208,6 +208,58 @@
 		lua_drop_global_lock();
 }
 
+/* below is an helper function to retrieve string on on Lua stack at <index>
+ * in a safe way (function may not LJMP). It can be useful to retrieve errors
+ * at the top of the stack from an unprotected environment.
+ *
+ * The returned string will is only valid as long as the value at <index> is
+ * not removed from the stack.
+ *
+ * It is assumed that the calling function is allowed to manipulate <L>
+ */
+__LJMP static int _hlua_tostring_safe(lua_State *L)
+{
+	const char **str = lua_touserdata(L, 1);
+	const char *cur_str = MAY_LJMP(lua_tostring(L, 2));
+
+	if (cur_str)
+		*str = cur_str;
+	return 0;
+}
+static const char *hlua_tostring_safe(lua_State *L, int index)
+{
+	const char *str = NULL;
+
+	if (!lua_checkstack(L, 4))
+		return NULL;
+
+	/* before any stack modification, save the targeted value on the top of
+	 * the stack: this will allow us to use relative index to target it.
+	 */
+	lua_pushvalue(L, index);
+
+	/* push our custom _hlua_tostring_safe() function on the stack, then push
+	 * our own string pointer and targeted value (at <index>) as argument
+	 */
+	lua_pushcfunction(L, _hlua_tostring_safe);
+	lua_pushlightuserdata(L, &str); // 1st func argument = string pointer
+	lua_pushvalue(L, -3);           // 2nd func argument = targeted value
+
+	lua_remove(L, -4); // remove <index> copy as we're done using it
+
+	/* call our custom function with proper arguments using pcall() to catch
+	 * exceptions (if any)
+	 */
+	switch (lua_pcall(L, 2, 0, 0)) {
+		case LUA_OK:
+			break;
+		default:
+			/* error was caught */
+			return NULL;
+	}
+	return str;
+}
+
 #define SET_SAFE_LJMP_L(__L, __HLUA) \
 	({ \
 		int ret; \
@@ -1820,7 +1872,7 @@
 			ret = HLUA_E_ERR;
 			break;
 		}
-		msg = lua_tostring(lua->T, -1);
+		msg = hlua_tostring_safe(lua->T, -1);
 		trace = hlua_traceback(lua->T, ", ");
 		if (msg)
 			lua_pushfstring(lua->T, "[state-id %d] runtime error: %s from %s", lua->state_id, msg, trace);
@@ -1845,7 +1897,7 @@
 			ret = HLUA_E_ERR;
 			break;
 		}
-		msg = lua_tostring(lua->T, -1);
+		msg = hlua_tostring_safe(lua->T, -1);
 		if (msg)
 			lua_pushfstring(lua->T, "[state-id %d] message handler error: %s", lua->state_id, msg);
 		else
@@ -8959,7 +9011,7 @@
 		SEND_ERR(NULL, "Lua task: execution timeout.\n");
 		goto err_task_abort;
 	case HLUA_E_ERRMSG:
-		SEND_ERR(NULL, "Lua task: %s.\n", lua_tostring(hlua->T, -1));
+		SEND_ERR(NULL, "Lua task: %s.\n", hlua_tostring_safe(hlua->T, -1));
 		goto err_task_abort;
 	case HLUA_E_ERR:
 	default:
@@ -9234,7 +9286,7 @@
 		break;
 
 	case HLUA_E_ERRMSG:
-		SEND_ERR(NULL, "Lua event_hdl: %s.\n", lua_tostring(hlua->T, -1));
+		SEND_ERR(NULL, "Lua event_hdl: %s.\n", hlua_tostring_safe(hlua->T, -1));
 		break;
 
 	case HLUA_E_ERR:
@@ -9573,7 +9625,7 @@
 		/* The following Lua calls can fail. */
 		if (!SET_SAFE_LJMP(hlua_sub->hlua)) {
 			if (lua_type(hlua_sub->hlua->T, -1) == LUA_TSTRING)
-				error = lua_tostring(hlua_sub->hlua->T, -1);
+				error = hlua_tostring_safe(hlua_sub->hlua->T, -1);
 			else
 				error = "critical error";
 			ha_alert("Lua event_hdl: %s.\n", error);
@@ -9868,7 +9920,7 @@
 		/* The following Lua calls can fail. */
 		if (!SET_SAFE_LJMP(stream->hlua)) {
 			if (lua_type(stream->hlua->T, -1) == LUA_TSTRING)
-				error = lua_tostring(stream->hlua->T, -1);
+				error = hlua_tostring_safe(stream->hlua->T, -1);
 			else
 				error = "critical error";
 			SEND_ERR(stream->be, "Lua converter '%s': %s.\n", fcn->name, error);
@@ -9940,7 +9992,7 @@
 	case HLUA_E_ERRMSG:
 		/* Display log. */
 		SEND_ERR(stream->be, "Lua converter '%s': %s.\n",
-		         fcn->name, lua_tostring(stream->hlua->T, -1));
+		         fcn->name, hlua_tostring_safe(stream->hlua->T, -1));
 		lua_pop(stream->hlua->T, 1);
 		return 0;
 
@@ -9993,7 +10045,7 @@
 		/* The following Lua calls can fail. */
 		if (!SET_SAFE_LJMP(stream->hlua)) {
 			if (lua_type(stream->hlua->T, -1) == LUA_TSTRING)
-				error = lua_tostring(stream->hlua->T, -1);
+				error = hlua_tostring_safe(stream->hlua->T, -1);
 			else
 				error = "critical error";
 			SEND_ERR(smp->px, "Lua sample-fetch '%s': %s.\n", fcn->name, error);
@@ -10066,7 +10118,7 @@
 	case HLUA_E_ERRMSG:
 		/* Display log. */
 		SEND_ERR(smp->px, "Lua sample-fetch '%s': %s.\n",
-		         fcn->name, lua_tostring(stream->hlua->T, -1));
+		         fcn->name, hlua_tostring_safe(stream->hlua->T, -1));
 		lua_pop(stream->hlua->T, 1);
 		return 0;
 
@@ -10323,7 +10375,7 @@
 		/* The following Lua calls can fail. */
 		if (!SET_SAFE_LJMP(s->hlua)) {
 			if (lua_type(s->hlua->T, -1) == LUA_TSTRING)
-				error = lua_tostring(s->hlua->T, -1);
+				error = hlua_tostring_safe(s->hlua->T, -1);
 			else
 				error = "critical error";
 			SEND_ERR(px, "Lua function '%s': %s.\n",
@@ -10417,7 +10469,7 @@
 	case HLUA_E_ERRMSG:
 		/* Display log. */
 		SEND_ERR(px, "Lua function '%s': %s.\n",
-		         rule->arg.hlua_rule->fcn->name, lua_tostring(s->hlua->T, -1));
+		         rule->arg.hlua_rule->fcn->name, hlua_tostring_safe(s->hlua->T, -1));
 		lua_pop(s->hlua->T, 1);
 		goto end;
 
@@ -10509,7 +10561,7 @@
 	/* The following Lua calls can fail. */
 	if (!SET_SAFE_LJMP(hlua)) {
 		if (lua_type(hlua->T, -1) == LUA_TSTRING)
-			error = lua_tostring(hlua->T, -1);
+			error = hlua_tostring_safe(hlua->T, -1);
 		else
 			error = "critical error";
 		SEND_ERR(strm->be, "Lua applet tcp '%s': %s.\n",
@@ -10592,7 +10644,7 @@
 	case HLUA_E_ERRMSG:
 		/* Display log. */
 		SEND_ERR(px, "Lua applet tcp '%s': %s.\n",
-		         rule->arg.hlua_rule->fcn->name, lua_tostring(hlua->T, -1));
+		         rule->arg.hlua_rule->fcn->name, hlua_tostring_safe(hlua->T, -1));
 		lua_pop(hlua->T, 1);
 		goto error;
 
@@ -10700,7 +10752,7 @@
 	/* The following Lua calls can fail. */
 	if (!SET_SAFE_LJMP(hlua)) {
 		if (lua_type(hlua->T, -1) == LUA_TSTRING)
-			error = lua_tostring(hlua->T, -1);
+			error = hlua_tostring_safe(hlua->T, -1);
 		else
 			error = "critical error";
 		SEND_ERR(strm->be, "Lua applet http '%s': %s.\n",
@@ -10801,7 +10853,7 @@
 		case HLUA_E_ERRMSG:
 			/* Display log. */
 			SEND_ERR(px, "Lua applet http '%s': %s.\n",
-				 rule->arg.hlua_rule->fcn->name, lua_tostring(hlua->T, -1));
+				 rule->arg.hlua_rule->fcn->name, hlua_tostring_safe(hlua->T, -1));
 			lua_pop(hlua->T, 1);
 			goto error;
 
@@ -11335,7 +11387,7 @@
 	/* The following Lua calls can fail. */
 	if (!SET_SAFE_LJMP(hlua)) {
 		if (lua_type(hlua->T, -1) == LUA_TSTRING)
-			error = lua_tostring(hlua->T, -1);
+			error = hlua_tostring_safe(hlua->T, -1);
 		else
 			error = "critical error";
 		SEND_ERR(NULL, "Lua cli '%s': %s.\n", fcn->name, error);
@@ -11420,7 +11472,7 @@
 	case HLUA_E_ERRMSG:
 		/* Display log. */
 		SEND_ERR(NULL, "Lua cli '%s': %s.\n",
-		         fcn->name, lua_tostring(hlua->T, -1));
+		         fcn->name, hlua_tostring_safe(hlua->T, -1));
 		lua_pop(hlua->T, 1);
 		return 1;
 
@@ -11674,21 +11726,21 @@
 		conf->ref[state_id] = flt_ref;
 		break;
 	case LUA_ERRRUN:
-		ha_alert("Lua filter '%s' : runtime error : %s", conf->reg->name, lua_tostring(L, -1));
+		ha_alert("Lua filter '%s' : runtime error : %s", conf->reg->name, hlua_tostring_safe(L, -1));
 		goto error;
 	case LUA_ERRMEM:
 		ha_alert("Lua filter '%s' : out of memory error", conf->reg->name);
 		goto error;
 	case LUA_ERRERR:
-		ha_alert("Lua filter '%s' : message handler error : %s", conf->reg->name, lua_tostring(L, -1));
+		ha_alert("Lua filter '%s' : message handler error : %s", conf->reg->name, hlua_tostring_safe(L, -1));
 		goto error;
 #if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM <= 503
 	case LUA_ERRGCMM:
-		ha_alert("Lua filter '%s' : garbage collector error : %s", conf->reg->name, lua_tostring(L, -1));
+		ha_alert("Lua filter '%s' : garbage collector error : %s", conf->reg->name, hlua_tostring_safe(L, -1));
 		goto error;
 #endif
 	default:
-		ha_alert("Lua filter '%s' : unknown error : %s", conf->reg->name, lua_tostring(L, -1));
+		ha_alert("Lua filter '%s' : unknown error : %s", conf->reg->name, hlua_tostring_safe(L, -1));
 		goto error;
 	}
 
@@ -11790,7 +11842,7 @@
 			const char *error;
 
 			if (lua_type(s->hlua->T, -1) == LUA_TSTRING)
-				error = lua_tostring(s->hlua->T, -1);
+				error = hlua_tostring_safe(s->hlua->T, -1);
 			else
 				error = "critical error";
 			SEND_ERR(s->be, "Lua filter '%s': %s.\n", conf->reg->name, error);
@@ -11845,7 +11897,7 @@
 		filter->ctx = flt_ctx;
 		break;
 	case HLUA_E_ERRMSG:
-		SEND_ERR(s->be, "Lua filter '%s' : %s.\n", conf->reg->name, lua_tostring(s->hlua->T, -1));
+		SEND_ERR(s->be, "Lua filter '%s' : %s.\n", conf->reg->name, hlua_tostring_safe(s->hlua->T, -1));
 		ret = -1;
 		goto end;
 	case HLUA_E_ETMOUT:
@@ -11923,7 +11975,7 @@
 			const char *error;
 
 			if (lua_type(flt_hlua->T, -1) == LUA_TSTRING)
-				error = lua_tostring(flt_hlua->T, -1);
+				error = hlua_tostring_safe(flt_hlua->T, -1);
 			else
 				error = "critical error";
 			SEND_ERR(s->be, "Lua filter '%s': %s.\n", conf->reg->name, error);
@@ -12031,7 +12083,7 @@
 		ret = 0;
 		goto end;
 	case HLUA_E_ERRMSG:
-		SEND_ERR(s->be, "Lua filter '%s' : %s.\n", conf->reg->name, lua_tostring(flt_hlua->T, -1));
+		SEND_ERR(s->be, "Lua filter '%s' : %s.\n", conf->reg->name, hlua_tostring_safe(flt_hlua->T, -1));
 		ret = -1;
 		goto end;
 	case HLUA_E_ETMOUT:
@@ -12520,7 +12572,7 @@
 	/* Just load and compile the file. */
 	error = luaL_loadfile(L, args[0]);
 	if (error) {
-		memprintf(err, "error in Lua file '%s': %s", args[0], lua_tostring(L, -1));
+		memprintf(err, "error in Lua file '%s': %s", args[0], hlua_tostring_safe(L, -1));
 		lua_pop(L, 1);
 		return -1;
 	}
@@ -12542,24 +12594,24 @@
 	case LUA_OK:
 		break;
 	case LUA_ERRRUN:
-		memprintf(err, "Lua runtime error: %s", lua_tostring(L, -1));
+		memprintf(err, "Lua runtime error: %s", hlua_tostring_safe(L, -1));
 		lua_pop(L, 1);
 		return -1;
 	case LUA_ERRMEM:
 		memprintf(err, "Lua out of memory error");
 		return -1;
 	case LUA_ERRERR:
-		memprintf(err, "Lua message handler error: %s", lua_tostring(L, -1));
+		memprintf(err, "Lua message handler error: %s", hlua_tostring_safe(L, -1));
 		lua_pop(L, 1);
 		return -1;
 #if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM <= 503
 	case LUA_ERRGCMM:
-		memprintf(err, "Lua garbage collector error: %s", lua_tostring(L, -1));
+		memprintf(err, "Lua garbage collector error: %s", hlua_tostring_safe(L, -1));
 		lua_pop(L, 1);
 		return -1;
 #endif
 	default:
-		memprintf(err, "Lua unknown error: %s", lua_tostring(L, -1));
+		memprintf(err, "Lua unknown error: %s", hlua_tostring_safe(L, -1));
 		lua_pop(L, 1);
 		return -1;
 	}
@@ -12711,7 +12763,7 @@
 		if (setjmp(safe_ljmp_env) != 0) {
 			lua_atpanic(L, hlua_panic_safe);
 			if (lua_type(L, -1) == LUA_TSTRING)
-				error = lua_tostring(L, -1);
+				error = hlua_tostring_safe(L, -1);
 			else
 				error = "critical error";
 			fprintf(stderr, "lua-prepend-path: %s.\n", error);
@@ -12980,7 +13032,7 @@
 	if (setjmp(safe_ljmp_env) != 0) {
 		lua_atpanic(L, hlua_panic_safe);
 		if (lua_type(L, -1) == LUA_TSTRING)
-			error = lua_tostring(L, -1);
+			error = hlua_tostring_safe(L, -1);
 		else
 			error = "critical error";
 		fprintf(stderr, "Lua post-init: %s.\n", error);
@@ -13014,7 +13066,7 @@
 		case LUA_ERRRUN:
 			if (!kind)
 				kind = "runtime error";
-			msg = lua_tostring(L, -1);
+			msg = hlua_tostring_safe(L, -1);
 			trace = hlua_traceback(L, ", ");
 			if (msg)
 				ha_alert("Lua init: %s: '%s' from %s\n", kind, msg, trace);
@@ -13275,7 +13327,7 @@
 	if (setjmp(safe_ljmp_env) != 0) {
 		lua_atpanic(L, hlua_panic_safe);
 		if (lua_type(L, -1) == LUA_TSTRING)
-			error_msg = lua_tostring(L, -1);
+			error_msg = hlua_tostring_safe(L, -1);
 		else
 			error_msg = "critical error";
 		fprintf(stderr, "Lua init: %s.\n", error_msg);