diff --git a/examples/rootfs b/examples/rootfs index d8d54fcd0..d8a9b0d6c 160000 --- a/examples/rootfs +++ b/examples/rootfs @@ -1 +1 @@ -Subproject commit d8d54fcd0835b9bcdbb146e9f277fc2eb69ff79f +Subproject commit d8a9b0d6c52a3c5bc627c055d5f711dacbb1a1f6 diff --git a/examples/src/linux/path_traverse.c b/examples/src/linux/path_traverse.c new file mode 100644 index 000000000..6d478e670 --- /dev/null +++ b/examples/src/linux/path_traverse.c @@ -0,0 +1,15 @@ +// gcc -static ~/qiling/examples/src/linux/path_traverse.c -g -O0 -o ~/qiling/examples/rootfs/x8664_linux/bin/path_traverse_static +// https://www.kalmarunionen.dk/writeups/2022/rwctf/qlaas/ +#include +#include + +int main(){ + char buf[4096]; + int fd = openat(1, "/etc/passwd", O_RDONLY); + ssize_t len = read(fd, buf, sizeof(buf)); + write(1, buf, len); + + fd = openat(1, "/etc/passwd_link", O_RDONLY); + len = read(fd, buf, sizeof(buf)); + write(1, buf, len); +} diff --git a/qiling/os/mapper.py b/qiling/os/mapper.py index e5e9382dd..738e51d38 100644 --- a/qiling/os/mapper.py +++ b/qiling/os/mapper.py @@ -95,14 +95,11 @@ def has_mapping(self, fm): def mapping_count(self): return len(self._mapping) - def open_ql_file(self, path, openflags, openmode, dir_fd=None): + def open_ql_file(self, path, openflags, openmode): if self.has_mapping(path): self.ql.log.info(f"mapping {path}") return self._open_mapping_ql_file(path, openflags, openmode) else: - if dir_fd: - return ql_file.open(path, openflags, openmode, dir_fd=dir_fd) - real_path = self.ql.os.path.transform_to_real_path(path) return ql_file.open(real_path, openflags, openmode) diff --git a/qiling/os/path.py b/qiling/os/path.py index 9872e4f30..b3914bcac 100644 --- a/qiling/os/path.py +++ b/qiling/os/path.py @@ -128,13 +128,16 @@ def transform_to_link_path(self, path: str) -> str: return str(real_path.absolute()) def transform_to_real_path(self, path: str) -> str: + # TODO: We really need a virtual file system. real_path = self.convert_path(self.ql.rootfs, self.cwd, path) if os.path.islink(real_path): link_path = Path(os.readlink(real_path)) - if not link_path.is_absolute(): - real_path = Path(os.path.join(os.path.dirname(real_path), link_path)) + real_path = self.convert_path(os.path.dirname(real_path), "/", link_path) + + if os.path.islink(real_path): + real_path = self.transform_to_real_path(real_path) # resolve multilevel symbolic link if not os.path.exists(real_path): diff --git a/qiling/os/posix/syscall/fcntl.py b/qiling/os/posix/syscall/fcntl.py index e7f515839..fe672ce9e 100644 --- a/qiling/os/posix/syscall/fcntl.py +++ b/qiling/os/posix/syscall/fcntl.py @@ -4,6 +4,7 @@ # import os +from pathlib import Path from qiling import Qiling from qiling.const import QL_OS, QL_ARCH @@ -101,11 +102,13 @@ def ql_syscall_openat(ql: Qiling, fd: int, path: int, flags: int, mode: int): fd = ql.unpacks(ql.pack(fd)) if 0 <= fd < NR_OPEN: - dir_fd = ql.os.fd[fd].fileno() - else: - dir_fd = None + fobj = ql.os.fd[fd] + # ql_file object or QlFsMappedObject + if hasattr(fobj, "fileno") and hasattr(fobj, "name"): + if not Path.is_absolute(Path(file_path)): + file_path = Path(fobj.name) / Path(file_path) - ql.os.fd[idx] = ql.os.fs_mapper.open_ql_file(file_path, flags, mode, dir_fd) + ql.os.fd[idx] = ql.os.fs_mapper.open_ql_file(file_path, flags, mode) regreturn = idx except QlSyscallError as e: diff --git a/tests/test_elf.py b/tests/test_elf.py index 86a35a5f2..e4f84378f 100644 --- a/tests/test_elf.py +++ b/tests/test_elf.py @@ -1091,6 +1091,15 @@ def test_memory_search(self): self.assertEqual([0x1000 + 11, 0x2000 + 11, 0x3000 + 43], ql.mem.search(b"\x09\x53(\x0f|\x1a|\x04)[^\x0d]")) del ql + + def test_elf_linux_x8664_path_traversion(self): + mock_stdout = pipe.SimpleOutStream(sys.stdout.fileno()) + ql = Qiling(["../examples/rootfs/x8664_linux/bin/path_traverse_static"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG, stdout=mock_stdout) + + ql.run() + self.assertTrue("root\n" not in ql.os.stdout.read().decode("utf-8")) + + del ql if __name__ == "__main__": unittest.main()