Bug 48313 (worried-after-failed-exec-in-forked-lua)

Summary: MEMORY_LEAK в rpm-4.13.0.1/luaext/lposix.c
Product: Sisyphus Reporter: e.bykhanova <e.bykhanova>
Component: rpmAssignee: placeholder <placeholder>
Status: RESOLVED LATER QA Contact: qa-sisyphus
Severity: normal    
Priority: P5 CC: antohami, at, glebfm, imz, ldv, placeholder, rider, vt
Version: unstable   
Hardware: x86_64   
OS: Linux   
Attachments:
Description Flags
Скриншот фрагмента кода none

Description e.bykhanova@fobos-nt.ru 2023-11-02 20:39:19 MSK
Created attachment 14962 [details]
Скриншот фрагмента кода

Динамическая память аллоцируется в lposix.c:354 посредством вызова ф-и 'malloc'. Далее выделенная память не освобождается, это приводит к утечке в lposix.c:360.

Прошу обратить внимание на факт того, что эта проблема пофикшена в master-ветке (https://github.com/rpm-software-management/rpm/blob/fa818208abdc68d1bc77c44cc1b5c5a17ea8ffd2/rpmio/lposix.c#L345-L351).

Found by Linux Verification Center (https://portal.linuxtesting.ru/) with SVACE.

Author E.Bykhanova.
Comment 1 Anton Farygin 2023-11-03 09:19:02 MSK
На Sisyphus проверяли ?
Comment 2 e.bykhanova@fobos-nt.ru 2023-11-05 14:49:42 MSK
Тестирование на версии из Sisyphus не проводилось
Comment 3 Ivan Zakharyaschev 2026-04-24 20:08:02 MSK
answer to https://bugzilla.altlinux.org/48313
[worried-after-failed-exec-in-forked-lua]

Спасибо за сообщение и указание на исправление в апстриме! Ясно. Речь про функцию Pexec. Не освобождается память, выделенная под argv.

Исправление можно применить при очередном релизе пакета (тривиальный патч cb2ae4bdf2f60876fdc68e3f84938e9c37182fab ) для того, чтобы всё выглядело хорошо и не беспокоило. Но это совсем не критичная утечка памяти:

Во-первых, только в случае неуспеха execvp релевантно это замечание.

Вот нынешний код из Sisyphus:

static int Pexec(lua_State *L)			/** exec(path,[args]) */
{
	const char *path = luaL_checkstring(L, 1);
	int i,n=lua_gettop(L);
	char **argv;

	if (!have_forked)
	    return luaL_error(L, "exec not permitted in this context");

	rpmSetCloseOnExec();

	argv = malloc((n+1)*sizeof(char*));
	if (argv==NULL) return luaL_error(L,"not enough memory");
	argv[0] = (char*)path;
	for (i=1; i<n; i++) argv[i] = (char*)luaL_checkstring(L, i+1);
	argv[i] = NULL;
	execvp(path,argv);
	return pusherror(L, path);
}

Во-вторых, это может делаться только после форка (в коде выше видна проверка), т.е. как обычно, в процессе, который специально был создан для выполнения внешней программы, которую как раз не удалось запустить. Будет ли после этого этот процесс ещё долго жить, чтобы эта утечка имела какое-то влияние?.. Своё предназначение (в разумной ситуации) этот процесс выполнил и будет уничтожен. Память уже не имеет значения.

В-третьих, это не открывает дополнительных возможностей злонамеренно атаковать систему через lua, потому что для этого надо иметь возможность выполнять rpm и выбирать от имени администратора, какие пакеты ставить (с этим lua-кодом), что уже само по себе позволит сделать многое.

Где в rpm используется lua, можно ознакомиться, например, на апстримной странице https://rpm.org/docs/4.19.x/manual/lua.html (у нас будет не всё).

* Lua scriptlets
* Lua macros

Lua scriptlets в устанавливаемых пакетах. Lua macros может в ALT и не быть. В любом случае, имея контроль над этими исполняемыми вещами можно и без утечки памяти выполнить более прямую атаку.

* * *

Для любопытства вот примеры lua scriptlets в ALT:

$ git -P -C /people/specbot/public/specs.git/ grep -Fe '<lua>' -A5 sisyphus
sisyphus:b/btrfs-progs/btrfs-progs.spec:%pretrans -p <lua>
sisyphus:b/btrfs-progs/btrfs-progs.spec-st = posix.stat("/usr/bin/btrfs")
sisyphus:b/btrfs-progs/btrfs-progs.spec-if st and st.type == "link" then
sisyphus:b/btrfs-progs/btrfs-progs.spec-  os.remove("/usr/bin/btrfs")
sisyphus:b/btrfs-progs/btrfs-progs.spec-end
sisyphus:b/btrfs-progs/btrfs-progs.spec-
--
sisyphus:c/ca-trust/ca-trust.spec:%pretrans -p <lua>
sisyphus:c/ca-trust/ca-trust.spec--- Blocklist symlinks become blacklist directories.
sisyphus:c/ca-trust/ca-trust.spec--- And blacklist directories become blocklist symlinks.
sisyphus:c/ca-trust/ca-trust.spec-paths = { "%_datadir/pki/ca-trust-source/", "%catrustdir/source/" }
sisyphus:c/ca-trust/ca-trust.spec-name_new = "blocklist"
sisyphus:c/ca-trust/ca-trust.spec-name_old = "blacklist"
--
sisyphus:e/example-pretrans-dir-to-symlink/dir-to-symlink.spec:Note that '%%pretrans' scriptlets MUST be written in Lua and thus use -p <lua>
sisyphus:e/example-pretrans-dir-to-symlink/dir-to-symlink.spec-in order to function during initial system installation when no shell has yet
sisyphus:e/example-pretrans-dir-to-symlink/dir-to-symlink.spec-been installed.
sisyphus:e/example-pretrans-dir-to-symlink/dir-to-symlink.spec-
sisyphus:e/example-pretrans-dir-to-symlink/dir-to-symlink.spec-%build
sisyphus:e/example-pretrans-dir-to-symlink/dir-to-symlink.spec-date > file.txt
--
sisyphus:e/example-pretrans-dir-to-symlink/dir-to-symlink.spec:%pretrans -p <lua>
sisyphus:e/example-pretrans-dir-to-symlink/dir-to-symlink.spec--- Define the path to directory being replaced below.
sisyphus:e/example-pretrans-dir-to-symlink/dir-to-symlink.spec--- DO NOT add a trailing slash at the end.
sisyphus:e/example-pretrans-dir-to-symlink/dir-to-symlink.spec-path = "%_libexecdir/%name/b"
sisyphus:e/example-pretrans-dir-to-symlink/dir-to-symlink.spec-st = posix.stat(path)
sisyphus:e/example-pretrans-dir-to-symlink/dir-to-symlink.spec-if st and st.type == "directory" then
--
sisyphus:e/example-pretrans-symlink-to-dir/symlink-to-dir.spec:Note that '%%pretrans' scriptlets MUST be written in Lua and thus use -p <lua>
sisyphus:e/example-pretrans-symlink-to-dir/symlink-to-dir.spec-in order to function during initial system installation when no shell has yet
sisyphus:e/example-pretrans-symlink-to-dir/symlink-to-dir.spec-been installed.
sisyphus:e/example-pretrans-symlink-to-dir/symlink-to-dir.spec-
sisyphus:e/example-pretrans-symlink-to-dir/symlink-to-dir.spec-%build
sisyphus:e/example-pretrans-symlink-to-dir/symlink-to-dir.spec-date > file.txt
--
sisyphus:e/example-pretrans-symlink-to-dir/symlink-to-dir.spec:%pretrans -p <lua>
sisyphus:e/example-pretrans-symlink-to-dir/symlink-to-dir.spec--- Define the path to the symlink being replaced below.
sisyphus:e/example-pretrans-symlink-to-dir/symlink-to-dir.spec-path = "%_libexecdir/%name/symlinked"
sisyphus:e/example-pretrans-symlink-to-dir/symlink-to-dir.spec-st = posix.stat(path)
sisyphus:e/example-pretrans-symlink-to-dir/symlink-to-dir.spec-if st and st.type == "link" then
sisyphus:e/example-pretrans-symlink-to-dir/symlink-to-dir.spec-  os.remove(path)
--
sisyphus:f/filesystem/filesystem.spec:%pretrans -p <lua>
sisyphus:f/filesystem/filesystem.spec-migrate = false
sisyphus:f/filesystem/filesystem.spec-if posix.stat("/usr") then
sisyphus:f/filesystem/filesystem.spec-  -- See if we need to migrate to merged-usr.
sisyphus:f/filesystem/filesystem.spec-  for i, d in pairs({
sisyphus:f/filesystem/filesystem.spec-"/bin",
--
sisyphus:f/firmware-alsa-sof/firmware-alsa-sof.spec:%pretrans -p <lua>
sisyphus:f/firmware-alsa-sof/firmware-alsa-sof.spec-path = "%{_firmwarepath}/intel/sof-tplg"
sisyphus:f/firmware-alsa-sof/firmware-alsa-sof.spec-st = posix.stat(path)
sisyphus:f/firmware-alsa-sof/firmware-alsa-sof.spec-if st and st.type == "link" then
sisyphus:f/firmware-alsa-sof/firmware-alsa-sof.spec-  os.remove(path)
sisyphus:f/firmware-alsa-sof/firmware-alsa-sof.spec-end
--
sisyphus:f/firmware-linux/linux-firmware.spec:%pretrans -p <lua>
sisyphus:f/firmware-linux/linux-firmware.spec-paths = {
sisyphus:f/firmware-linux/linux-firmware.spec-  "/lib/firmware/nvidia/ad103",
sisyphus:f/firmware-linux/linux-firmware.spec-  "/lib/firmware/nvidia/ad104",
sisyphus:f/firmware-linux/linux-firmware.spec-  "/lib/firmware/nvidia/ad106",
sisyphus:f/firmware-linux/linux-firmware.spec-  "/lib/firmware/nvidia/ad107"
--
sisyphus:g/gajim/gajim.spec:%pretrans -p <lua>
sisyphus:g/gajim/gajim.spec--- see http://git.altlinux.org/gears/e/example-pretrans-dir-to-symlink.git
sisyphus:g/gajim/gajim.spec--- Define the path to directory being replaced below.
sisyphus:g/gajim/gajim.spec--- DO NOT add a trailing slash at the end.
sisyphus:g/gajim/gajim.spec-path = "%python3_sitelibdir/%name/data"
sisyphus:g/gajim/gajim.spec-st = posix.stat(path)
--
sisyphus:t/tuxguitar/tuxguitar.spec:%pretrans -p <lua>
sisyphus:t/tuxguitar/tuxguitar.spec-path = "%{_iconsdir}/hicolor/96x96/apps/tuxguitar.png"
sisyphus:t/tuxguitar/tuxguitar.spec-st = posix.stat(path)
sisyphus:t/tuxguitar/tuxguitar.spec-if st and st.type == "directory" then
sisyphus:t/tuxguitar/tuxguitar.spec-  if posix.stat(path .. "/icon.png") then
sisyphus:t/tuxguitar/tuxguitar.spec-    os.remove(path .. "/icon.png")
--
sisyphus:u/usrmerge/usrmerge.spec:%pre ensure -p <lua>
sisyphus:u/usrmerge/usrmerge.spec-hier_convert_prog = "%_prefix/libexec/usrmerge/hier-convert"
sisyphus:u/usrmerge/usrmerge.spec--- Ensure log messages are line-buffered.
sisyphus:u/usrmerge/usrmerge.spec-os.execute("printf '%%s\n' '%name-ensure-%EVR: Starting usrmerge-hier-convert...'")
sisyphus:u/usrmerge/usrmerge.spec-assert(os.execute(hier_convert_prog))
sisyphus:u/usrmerge/usrmerge.spec-