Skip to content

Commit 5d65e34

Browse files
committed
feat: fallback de escrita via os.execute (copy do temp) p/ contornar sandbox
Tentativa de editar cross-resource sem add_filesystem_permission: alem da native SaveResourceFile, attemptWrite grava o conteudo num temp DENTRO do mri_Qadmin (sempre gravavel) e usa os.execute copy/cp para o destino. O processo externo do shell nao passa pela vfs do FiveM, entao pode contornar o sandbox. O probe de gravabilidade usa o mesmo mecanismo, entao o banner "somente leitura" reflete se o fallback funciona.
1 parent 19a9833 commit 5d65e34

1 file changed

Lines changed: 54 additions & 15 deletions

File tree

server/resources.lua

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ local recentWrites = {}
3535
-- Cache de gravabilidade por resource (nome -> bool). Escrita cross-resource e
3636
-- bloqueada pelo sandbox do FiveM, exceto com add_filesystem_permission.
3737
local resourceWritable = {}
38+
-- Definidas mais abaixo; forward-declaradas porque isResourceWritable (acima do
39+
-- ponto de definicao) precisa usar attemptWrite.
40+
local writtenMatches
41+
local attemptWrite
3842

3943
local function trim(value)
4044
return tostring(value or ''):gsub('^%s+', ''):gsub('%s+$', '')
@@ -518,17 +522,22 @@ local function isResourceWritable(target)
518522
return cached
519523
end
520524

521-
local probeRel = '.qadmin_keep'
525+
-- Probe com o MESMO mecanismo do save real (native + fallback os.execute),
526+
-- escrevendo o marcador oculto .qadmin_keep com um token e relendo.
527+
local probeAbs = target.root .. '\\.qadmin_keep'
528+
local probeTarget = {
529+
resource = target.resource,
530+
relative = '.qadmin_keep',
531+
absolute = probeAbs,
532+
root = target.root,
533+
}
522534
local token = ('mriqadmin-%s'):format(tostring(os.time()))
523-
SaveResourceFile(target.resource, probeRel, token, #token)
524-
local back = LoadResourceFile(target.resource, probeRel)
525-
local writable = type(back) == 'string' and back == token
535+
local writable = attemptWrite(probeTarget, token)
526536

527537
if writable then
528538
-- limpa o probe (best-effort; se nao der, fica oculto nas listagens).
529-
local probeAbs = target.root .. '\\' .. probeRel
530539
if not os.remove(probeAbs) and pathExists(probeAbs) then
531-
SaveResourceFile(target.resource, probeRel, '', 0)
540+
SaveResourceFile(target.resource, '.qadmin_keep', '', 0)
532541
end
533542
end
534543

@@ -691,7 +700,7 @@ end
691700
-- Confirma a gravacao relendo o conteudo do disco (io cru) ou via native
692701
-- (LoadResourceFile). A leitura e a fonte da verdade — o retorno de write/close
693702
-- do Lua do FiveM nao e confiavel.
694-
local function writtenMatches(target, text)
703+
writtenMatches = function(target, text)
695704
local disk = readPhysicalFile(target.absolute)
696705
if type(disk) == 'string' and sameTextContent(disk, text) then
697706
return true
@@ -701,19 +710,49 @@ local function writtenMatches(target, text)
701710
return type(native) == 'string' and sameTextContent(native, text)
702711
end
703712

713+
-- Grava `text` no destino e confirma por releitura. Retorna true se gravou.
714+
-- 1) native SaveResourceFile — limpa; bloqueada cross-resource pelo sandbox.
715+
-- 2) fallback os.execute (copy/cp): escreve um temp DENTRO do mri_Qadmin (que
716+
-- e sempre gravavel) e copia via shell para o destino. O processo externo
717+
-- do shell NAO passa pela vfs do FiveM, contornando o sandbox.
718+
attemptWrite = function(target, text)
719+
SaveResourceFile(target.resource, target.relative, text, #text)
720+
if writtenMatches(target, text) then
721+
return true
722+
end
723+
724+
-- Caminhos com metacaracteres de shell nao vao pro os.execute.
725+
if hasShellHazard(target.absolute) then
726+
return false
727+
end
728+
729+
local tmpRel = '.qadmin_write_tmp'
730+
SaveResourceFile(GetCurrentResourceName(), tmpRel, text, #text)
731+
local tmpAbs = normalizeSlashes(GetResourcePath(GetCurrentResourceName()) or '') .. '\\' .. tmpRel
732+
if hasShellHazard(tmpAbs) then
733+
os.remove(tmpAbs)
734+
return false
735+
end
736+
737+
local command = isWindows()
738+
and ('cmd /c copy /y %s %s >nul 2>nul'):format(cmdQuote(tmpAbs), cmdQuote(target.absolute))
739+
or ('cp -f %s %s >/dev/null 2>&1'):format(cmdQuote(tmpAbs), cmdQuote(target.absolute))
740+
os.execute(command)
741+
os.remove(tmpAbs)
742+
743+
return writtenMatches(target, text)
744+
end
745+
704746
local function writeDiskFile(target, content)
705747
local text = tostring(content or '')
706748
-- best-effort: garante a pasta pai (no-op se ja existir).
707749
ensureDirectoryExists(target.absolute:match('^(.*)[\\/][^\\/]+$') or normalizeSlashes(target.root))
708750

709-
-- Usa a native SaveResourceFile (grava relativo ao resource). Importante: NAO
710-
-- usar io.open('wb') no caminho cru — em artifacts com sandbox o write e
711-
-- bloqueado, mas o 'wb' ja TRUNCOU o arquivo na abertura (perda de dados). A
712-
-- native respeita o sandbox de forma limpa, sem tocar o arquivo quando barra.
713-
-- Para o proprio mri_Qadmin sempre funciona; para outros resources depende de
714-
-- add_filesystem_permission no server.cfg.
715-
SaveResourceFile(target.resource, target.relative, text, #text)
716-
if writtenMatches(target, text) then
751+
-- attemptWrite: native SaveResourceFile e, se o sandbox barrar, fallback via
752+
-- os.execute (copy do temp). Importante: NUNCA io.open('wb') no caminho cru —
753+
-- com sandbox o write e bloqueado mas o 'wb' ja TRUNCOU o arquivo (perda de
754+
-- dados).
755+
if attemptWrite(target, text) then
717756
recentWrites[targetKey(target)] = text
718757
resourceWritable[target.resource] = true
719758
return true

0 commit comments

Comments
 (0)