Validate zip symlinks and backslash paths before extraction
- treat `\` as a separator in safe_relative so `\abs` and `..\..\x` are refused - read zip symlink targets with `unzip -p` off the `unzip -Z -l` listing and reject escapes before unpacking
This commit is contained in:
@@ -142,6 +142,14 @@ describe('safe_relative', function()
|
||||
assert_nil(fsmod.safe_relative('a/../../evil'))
|
||||
assert_nil(fsmod.safe_relative(''))
|
||||
end)
|
||||
|
||||
it('treats a backslash as a separator', function()
|
||||
assert_nil(fsmod.safe_relative('\\absolute_backslash.txt'))
|
||||
assert_nil(fsmod.safe_relative('..\\..\\..\\deep_escape.txt'))
|
||||
assert_nil(fsmod.safe_relative('a\\..\\..\\evil'))
|
||||
assert_nil(fsmod.safe_relative('C:\\windows\\evil'))
|
||||
assert_eq('lua/plug.lua', fsmod.safe_relative('lua\\plug.lua'))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('extract', function()
|
||||
@@ -257,6 +265,83 @@ describe('extract', function()
|
||||
assert_match('plug/lua/escape', result.err)
|
||||
end)
|
||||
|
||||
local function sh(root, script)
|
||||
vim.fn.system({ 'sh', '-c', 'cd ' .. vim.fn.shellescape(root) .. ' && ' .. script })
|
||||
return vim.v.shell_error == 0
|
||||
end
|
||||
|
||||
-- A symlink out of the destination followed by a member written *through* it.
|
||||
-- unzip would create the link and only then notice, so the whole archive has
|
||||
-- to be refused off the listing, before a single byte lands.
|
||||
it('refuses a zip that writes through an escaping symlink', function()
|
||||
local root = tmpdir()
|
||||
local made = sh(
|
||||
root,
|
||||
'mkdir -p src && cd src && ln -s ../../../outside link && zip -qy ../evil.zip link'
|
||||
.. ' && rm link && mkdir link && echo pwned > link/pwned.txt && zip -qgy ../evil.zip link/pwned.txt'
|
||||
)
|
||||
if not made then
|
||||
return
|
||||
end
|
||||
|
||||
local result = wait_extract(root .. '/evil.zip', root .. '/dest')
|
||||
assert_true(not result.ok)
|
||||
assert_match('refusing to extract', result.err)
|
||||
assert_match('is a symlink to', result.err)
|
||||
assert_match('../../../outside', result.err)
|
||||
assert_true(not fsmod.exists(root .. '/dest'))
|
||||
assert_true(not fsmod.exists(root .. '/outside'))
|
||||
assert_true(not fsmod.exists(vim.fs.dirname(root) .. '/outside'))
|
||||
end)
|
||||
|
||||
it('refuses a zip whose symlink points at an absolute path', function()
|
||||
local root = tmpdir()
|
||||
if not sh(root, 'mkdir -p src/plug && ln -s /etc/passwd src/plug/escape && cd src && zip -qry ../abs.zip plug') then
|
||||
return
|
||||
end
|
||||
|
||||
local result = wait_extract(root .. '/abs.zip', root .. '/dest')
|
||||
assert_true(not result.ok)
|
||||
assert_match('refusing to extract', result.err)
|
||||
assert_match('plug/escape', result.err)
|
||||
assert_true(not fsmod.exists(root .. '/dest'))
|
||||
end)
|
||||
|
||||
it('allows a zip symlink that stays inside the destination', function()
|
||||
local root = tmpdir()
|
||||
if
|
||||
not sh(
|
||||
root,
|
||||
'mkdir -p src/plug/lua && echo "return {}" > src/plug/lua/plug.lua'
|
||||
.. ' && ln -s lua/plug.lua src/plug/alias && cd src && zip -qry ../ok.zip plug'
|
||||
)
|
||||
then
|
||||
return
|
||||
end
|
||||
|
||||
local result = wait_extract(root .. '/ok.zip', root .. '/dest')
|
||||
assert_true(result.ok, tostring(result.err))
|
||||
assert_true(fsmod.exists(root .. '/dest/plug/alias'))
|
||||
end)
|
||||
|
||||
it('reads the symlink target of a member whose name looks like a glob', function()
|
||||
local root = tmpdir()
|
||||
if
|
||||
not sh(
|
||||
root,
|
||||
'mkdir -p src/plug && ln -s ../../../outside "src/plug/od[d]*link" && cd src && zip -qry ../glob.zip plug'
|
||||
)
|
||||
then
|
||||
return
|
||||
end
|
||||
|
||||
local result = wait_extract(root .. '/glob.zip', root .. '/dest')
|
||||
assert_true(not result.ok)
|
||||
assert_match('refusing to extract', result.err)
|
||||
assert_match('is a symlink to', result.err)
|
||||
assert_true(not fsmod.exists(root .. '/dest'))
|
||||
end)
|
||||
|
||||
it('allows a symlink that stays inside the destination', function()
|
||||
local root = tmpdir()
|
||||
vim.fn.mkdir(root .. '/src/plug/lua', 'p')
|
||||
|
||||
Reference in New Issue
Block a user