mirror of
https://github.com/openSUSE/osc.git
synced 2025-01-27 15:06:15 +01:00
Forbid extracting files with absolute path from 'cpio' archives (boo#1122683)
Also fix and modernize the code, add tests.
This commit is contained in:
parent
d61b781976
commit
5cbd110a84
@ -20,10 +20,6 @@ import struct
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
# workaround for python24
|
|
||||||
if not hasattr(os, 'SEEK_SET'):
|
|
||||||
os.SEEK_SET = 0
|
|
||||||
|
|
||||||
# format implementation is based on src/copyin.c and src/util.c (see cpio sources)
|
# format implementation is based on src/copyin.c and src/util.c (see cpio sources)
|
||||||
|
|
||||||
|
|
||||||
@ -129,7 +125,16 @@ class CpioRead:
|
|||||||
msg = '\'%s\' is no regular file - only regular files are supported atm' % hdr.filename
|
msg = '\'%s\' is no regular file - only regular files are supported atm' % hdr.filename
|
||||||
raise NotImplementedError(msg)
|
raise NotImplementedError(msg)
|
||||||
self.__file.seek(hdr.dataoff, os.SEEK_SET)
|
self.__file.seek(hdr.dataoff, os.SEEK_SET)
|
||||||
|
|
||||||
|
if fn.startswith(b"/"):
|
||||||
|
raise CpioError(fn, "Extracting files with absolute paths is not supported for security reasons")
|
||||||
|
|
||||||
fn = os.path.join(dest, fn)
|
fn = os.path.join(dest, fn)
|
||||||
|
|
||||||
|
dir_path, _ = os.path.split(fn)
|
||||||
|
if dir_path:
|
||||||
|
os.makedirs(dir_path, exist_ok=True)
|
||||||
|
|
||||||
with open(fn, 'wb') as f:
|
with open(fn, 'wb') as f:
|
||||||
f.write(self.__file.read(hdr.filesize))
|
f.write(self.__file.read(hdr.filesize))
|
||||||
os.chmod(fn, hdr.mode)
|
os.chmod(fn, hdr.mode)
|
||||||
@ -183,12 +188,22 @@ class CpioRead:
|
|||||||
to a dir the file will be stored in dest/filename. In case new_fn is specified
|
to a dir the file will be stored in dest/filename. In case new_fn is specified
|
||||||
the file will be stored as new_fn.
|
the file will be stored as new_fn.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# accept str for better user experience
|
||||||
|
if isinstance(filename, str):
|
||||||
|
filename = filename.encode("utf-8")
|
||||||
|
if isinstance(dest, str):
|
||||||
|
dest = dest.encode("utf-8")
|
||||||
|
if isinstance(new_fn, str):
|
||||||
|
new_fn = new_fn.encode("utf-8")
|
||||||
|
|
||||||
hdr = self._get_hdr(filename)
|
hdr = self._get_hdr(filename)
|
||||||
if not hdr:
|
if not hdr:
|
||||||
raise CpioError(filename, '\'%s\' does not exist in archive' % filename)
|
raise CpioError(filename, '\'%s\' does not exist in archive' % filename)
|
||||||
dest = dest or os.getcwdb()
|
dest = dest or os.getcwdb()
|
||||||
fn = new_fn or filename
|
fn = new_fn or filename
|
||||||
self._copyin_file(hdr, dest, fn)
|
self._copyin_file(hdr, dest, fn)
|
||||||
|
return os.path.join(dest, fn).decode("utf-8")
|
||||||
|
|
||||||
def copyin(self, dest=None):
|
def copyin(self, dest=None):
|
||||||
"""
|
"""
|
||||||
|
7
tests/fixtures/README
vendored
7
tests/fixtures/README
vendored
@ -26,3 +26,10 @@ Create archive.ar
|
|||||||
ar qP archive.ar /tmp/foo /123 very-long-long-long-long-name very-long-long-long-long-name2 'very-long-name
|
ar qP archive.ar /tmp/foo /123 very-long-long-long-long-name very-long-long-long-long-name2 'very-long-name
|
||||||
-with-newline' 'a
|
-with-newline' 'a
|
||||||
b' dir/file
|
b' dir/file
|
||||||
|
|
||||||
|
|
||||||
|
Create archive.cpio
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
printf "/tmp/foo\0/123\0very-long-long-long-long-name\0very-long-long-long-long-name2\0very-long-name
|
||||||
|
-with-newline\0a\nb\0dir/file\0" | cpio -ocv0 --owner=root:root > archive.cpio
|
||||||
|
BIN
tests/fixtures/archive.cpio
vendored
Normal file
BIN
tests/fixtures/archive.cpio
vendored
Normal file
Binary file not shown.
71
tests/test_util_cpio.py
Normal file
71
tests/test_util_cpio.py
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from osc.util.cpio import CpioRead
|
||||||
|
from osc.util.cpio import CpioError
|
||||||
|
|
||||||
|
|
||||||
|
FIXTURES_DIR = os.path.join(os.path.dirname(__file__), "fixtures")
|
||||||
|
|
||||||
|
|
||||||
|
class TestCpio(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.tmpdir = tempfile.mkdtemp(prefix="osc_test_")
|
||||||
|
try:
|
||||||
|
self.old_cwd = os.getcwd()
|
||||||
|
except FileNotFoundError:
|
||||||
|
self.old_cwd = os.path.expanduser("~")
|
||||||
|
os.chdir(self.tmpdir)
|
||||||
|
self.archive = os.path.join(FIXTURES_DIR, "archive.cpio")
|
||||||
|
self.cpio = CpioRead(self.archive)
|
||||||
|
self.cpio.read()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
os.chdir(self.old_cwd)
|
||||||
|
shutil.rmtree(self.tmpdir)
|
||||||
|
|
||||||
|
def test_file_list(self):
|
||||||
|
actual = [i.filename for i in self.cpio]
|
||||||
|
expected = [
|
||||||
|
# absolute path
|
||||||
|
b"/tmp/foo",
|
||||||
|
# this is a filename, not a long filename reference
|
||||||
|
b"/123",
|
||||||
|
b"very-long-long-long-long-name",
|
||||||
|
b"very-long-long-long-long-name2",
|
||||||
|
# long file name with a newline
|
||||||
|
b"very-long-name\n-with-newline",
|
||||||
|
# short file name with a newline
|
||||||
|
b"a\nb",
|
||||||
|
b"dir/file",
|
||||||
|
]
|
||||||
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
|
def test_copyin_file(self):
|
||||||
|
path = self.cpio.copyin_file("a\nb", dest=self.tmpdir)
|
||||||
|
|
||||||
|
# check that we've got the expected path
|
||||||
|
self.assertEqual(path, os.path.join(self.tmpdir, "a\nb"))
|
||||||
|
|
||||||
|
# ... and that the contents also match
|
||||||
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
|
self.assertEqual(f.read(), "newline\n")
|
||||||
|
|
||||||
|
def test_copyin_file_abspath(self):
|
||||||
|
self.assertRaises(CpioError, self.cpio.copyin_file, "/tmp/foo")
|
||||||
|
|
||||||
|
def test_copyin_file_subdir(self):
|
||||||
|
path = self.cpio.copyin_file("dir/file", dest=self.tmpdir)
|
||||||
|
|
||||||
|
# check that we've got the expected path
|
||||||
|
self.assertEqual(path, os.path.join(self.tmpdir, "dir/file"))
|
||||||
|
|
||||||
|
# ... and that the contents also match
|
||||||
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
|
self.assertEqual(f.read(), "file-in-a-dir\n")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
Loading…
Reference in New Issue
Block a user