Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import os
- import sys
- import errno
- from os.path import realpath
- import re
- import stat
- import fuse
- import logging
- from fuse import Direntry, Fuse, Stat
- # Emulates a /proc filesystem but allows only reading /proc/self/exe path
- fuse.fuse_python_api = (0, 2)
- logger = logging.getLogger('fproc')
- logging.basicConfig(level=logging.INFO)
- def find_pid_in_process_namespace(host_pid: int) -> int:
- with open(f"/proc/{host_pid}/status", "r", encoding="ascii") as f:
- data = f.read(100_000)
- if m := re.search(r'^NSpid\s*:([\s\d]+)$', data, re.MULTILINE | re.IGNORECASE):
- pids = m[1].split()
- return int(pids[-1])
- logger.warning(f"Cannot find container PID for host PID {host_pid} in proc")
- raise OSError(errno.EINVAL, "Cannot find children PID in its namespace")
- class FakeProc(Fuse):
- def _is_path_visible(self, container_pid: int, path: str) -> bool:
- directory, file = os.path.split(path)
- if path == '/':
- return True
- return self._is_item_visible(container_pid, directory, file)
- def _is_item_visible(self, container_pid: int, path: str, name: str) -> bool:
- if path == '/':
- return name in ('self', str(container_pid))
- if path == '/self' or path == f"/{container_pid}":
- return name in ('exe')
- return False
- def _check_access(self, path: str, container_pid: int, op: str):
- if not self._is_path_visible(container_pid, path):
- logger.info(f"DENY {op} on {path} by ctnr PID {container_pid}")
- raise OSError(errno.ENOENT, "Access denied to {path} for cntr PID {container_pid}")
- # print(f"ALLOW {op} on {path} by cntr PID {container_pid}")
- def _replace_self(self, path: str, container_pid: int) -> str:
- return re.sub(r'/self(?=/|\Z)', f"/{container_pid}", path)
- def _convert_to_host_path(self, path: str, container_pid: int, host_pid: int) -> str:
- assert not path.startswith('/self')
- segments = path.split('/')
- if segments[1] == str(container_pid):
- segments[1] = str(host_pid)
- elif segments[1] != '':
- raise ValueError(f"Invalid container path {path} given (cntr PID={container_pid})")
- return '/'.join(segments)
- def _transform_path(self, path: str, op: str) -> tuple[str, str]:
- host_pid, container_pid = self._get_child_pids()
- self._check_access(path, container_pid, op)
- container_path = self._replace_self(path, container_pid)
- host_path = self._convert_to_host_path(container_path, container_pid, host_pid)
- assert host_path.startswith('/')
- return container_path, f"/proc{host_path}"
- def _get_child_pids(self) -> tuple[int, int]:
- host_pid = self.GetContext()['pid']
- container_pid = find_pid_in_process_namespace(host_pid)
- return host_pid, container_pid
- def readlink(self, path: str):
- cntr_path, host_path = self._transform_path(path, 'readlink')
- # Special case
- if path == '/self':
- host_pid, container_pid = self._get_child_pids()
- return f"{container_pid}"
- return os.readlink(host_path)
- def getattr(self, path: str):
- cntr_path, host_path = self._transform_path(path, 'getattr')
- if path == '/self':
- return Stat(
- st_mode = stat.S_IFLNK | stat.S_IRUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH,
- st_nlink = 2,
- st_ino = 1,
- st_uid = 0,
- st_gid = 0,
- st_size = len(self.readlink(path))
- )
- return os.lstat(host_path)
- def readdir(self, path: str, offset):
- cntr_path, host_path = self._transform_path(path, 'readdir')
- host_pid, container_pid = self._get_child_pids()
- with os.scandir(host_path) as it:
- for entry in it:
- if not self._is_item_visible(container_pid, cntr_path, entry.name):
- # print(f"HIDE name {entry.name}")
- continue
- # print(f"ALLOW Show {entry.name} at {cntr_path}")
- # print(entry.is_symlink())
- yield Direntry(entry.name,
- ino=entry.inode(),
- type=(stat.S_IFLNK if entry.is_symlink() else
- stat.S_IFDIR if entry.is_dir(follow_symlinks = False) else
- stat.S_IFREG if entry.is_file(follow_symlinks = False)
- else None)
- )
- def access(self, path: str, mode):
- path, host_path = self._transform_path(path, 'access')
- if not os.access(host_path, mode):
- return -errno.EACCES
- def main():
- usage="""
- Fakeproc - allow access to /proc/self/exe in a sanbox (using FUSE)
- """ + Fuse.fusage
- server = FakeProc(version="%prog " + fuse.__version__,
- usage=usage,
- dash_s_do='setsingle')
- server.parse(errex=1)
- server.main()
- if __name__ == '__main__':
- main()
Advertisement
Add Comment
Please, Sign In to add comment