1 Commits

Author SHA1 Message Date
6d312905af Update README
Goes for all branches
2024-11-23 13:13:47 +01:00
3 changed files with 106 additions and 184 deletions

View File

@@ -1,3 +1,38 @@
# PyJail
Jailing tool for Python
PyJail is a jailing tool for Python
It allows you to jail Python programs in a closed off filesystem
## How to install
Installing PyJail is really simple! Just run `python3 ./install.py` in the directory
where the files are stored!
## Compatibility
As of now we're still working on a custom Python interpreter to make all programs
fully jailing compatible, sadly enough it's quite hard work.
So as of now it is compatible with all Python programs, **but** only some will be
properly confined.
## POSIX compatibility
We're also providing a (sort-of) POSIX compatible mode. This allows Python programs
made for Linux to also run natively on Windows. This is more meant for Windows
versions that don't feature WSL (Windows 7/8/8.1) but still need to run some
Linux only scripts.
### The 4 branches
Which branch works best for you?
Well, that's pretty simple. We have 4 branches (`main`, `next`, `edge` and `no-posix`).
If you want the most stable experience, then the `main` branch is for you.
If you want the lastest features, but also a more stable experience (compared to `edge`),
then the `next` branch is for you
If you want the bleeding-edge and don't care about stability, then `edge` is for you.
If you only need simple jailing and no POSIX compatibility, then `no-posix` is for you.
### Issues
Please report issues [over here](https://git.novacow.ch/Nova/PyJail/issues/)
And please check if your issue isn't a duplicate before reporting.

213
main.py
View File

@@ -1,155 +1,58 @@
"""
This is the PyJail, a jailing tool for running Python apps in a sandboxed environment.
Version: 0.2.1-next1
"""
import os
import time
import runpy
class PyJail:
"""
The jail manager, handles all system calls and such.
"""
def __init__(self, debug=False):
self.rootpath = ""
self.rootpath = self.fs()
self._debug = debug
with open(self.fs("/proc/klog"), "w") as f:
# Always use jailmgr.msg() from this point onwards.
f.write(f"[{time.time}] [jailmgr.__init__()] [INFO] START LOG")
f.close()
with open(self.fs("/proc/kproc"), "w") as f:
f.write("proc: jailmgr(1)")
self._program_counter = 2
f.close()
self._resolve_symlinks = False if os.path.isdir(self.fs("/bin")) else True
def run_program(self, path_to_bin):
"""
Runs a specified program.
"""
path_to_bin = self.fs(path_to_bin)
# print(path_to_bin)
# print(str(self.rootpath) + str(path_to_bin))
if path_to_bin == 3 or path_to_bin == 2:
self.msg("jailmgr.run_program()", "An error has occurred launching the program.", True,
"WARNING")
else:
with open(self.fs("/proc/kproc"), "a+") as f:
f.write(f"proc: {path_to_bin}({self._program_counter})")
self._program_counter += 1
runpy.run_path(path_to_bin)
def msg(self, caller: str, message: str, emit: bool = False, log_level: str = "INFO"):
"""
The custom message parser, can parse messages and alert apps of said messages.
Replaces print statements.
Args:
caller: The program that called the logger
message: Is the message to parse.
emit: If the message needs to be passed to apps.
log_level: The loglevel, either DEBUG, INFO, WARNING, ERROR, CRITICAL
"""
if self._debug is True:
emit = True
accepted_log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
if log_level.upper() not in accepted_log_levels:
self.msg(f"jailmgr.msg()",f"Not accepted loglevel!! {log_level}", False, "ERROR")
with open(self.fs("/proc/klog"), "a+") as f:
f.write(f"[{time.time}] [{caller}] [{log_level}] {message}")
if emit is True:
print(message)
return 0
def fs(self, check_path=None, resolve_symlinks=True):
"""
Keeps track of the jailed filesystem and makes sure any calls to any
file get done in the jailed filesystem
"""
if check_path is not None:
if os.path.exists(f"{self.rootpath}{check_path}"):
if self._resolve_symlinks is True or resolve_symlinks is True:
# This exists to ease /bin and other symlinks that are always present.
# It allows the system to drastically speed up resolving symlinks.
symlinkable_dirs = ["/bin", "/sbin", "/lib", "/lib64"]
for directory in symlinkable_dirs:
if check_path.startswith(directory):
check_path_usr_merge = f"/usr{check_path}"
return self.rootpath + check_path_usr_merge
# Well, if it doesn't start with any of them, we need to check each and every directory.
check_path_split = check_path.split("/", -1)
prepend_path = ""
for i, path in enumerate(check_path_split):
prepend_path = f"{prepend_path}/{path}" if not path.endswith("/") else f"{prepend_path}/{path}/"
check_path_split[i] = f"{prepend_path}"
check_path = ""
for i, path in enumerate(check_path_split):
if os.path.isdir(path) and i != (len(check_path_split) - 1):
# Directory is not a symlink, we can just ignore and move on.
pass
elif os.path.isdir(path) and i == (len(check_path_split) - 1):
# The last thing to access was a directory. We can safely return the full path now.
return path
elif not os.path.isdir(path) and i != (len(check_path_split) - 1):
# This was not the last thing we needed to access, so we assume it's a symlink.
# One problem is that we can't be sure, so we make sure it is a symlink.
with open(self.fs(path, False)) as f:
is_symlink = f.read()
f.close()
if is_symlink.startswith("symlnk"):
# This is a symlink!
# Symlinks always contain the full literal path that they need to access, so we can
# take that and do the same trick to split it and add the next things to it.
# raise NotImplementedError()
is_symlink_split = is_symlink.split(" ", 1)
symlink_dest = is_symlink_split[1]
symlink_dest = f"{symlink_dest}/{path}"
return symlink_dest
else:
# This is either not a symlink or an improperly configured one.
self.msg("jailmgr.fs()", "reached non-symlink file not at end of list",
False, "ERROR")
self.msg("jailmgr.fs()", "What was assumed to be a directory isn't a"
" directory nor a symlink! This might be because of a "
"typo or misconfigured symlink. The directory in question: "
f"{path}", True, "WARNING")
return 2
return self.rootpath + check_path
if check_path.startswith("."):
check_path = check_path.lstrip(".")
return os.getcwd() + check_path if "vfs" in os.getcwd() else 2
elif self.rootpath in check_path:
self.msg(f"jailmgr.fs()", "Cannot parse rootpath, expected vfspath", log_level="ERROR")
return 3
else:
# Path is not in the jailed fs, so we say it doesn't exist.
self.msg(f"jailmgr.fs()", "File/directory doesn't exist in vfspath", log_level="ERROR")
return 2
else:
rootpath = os.getcwd() + "/vfs"
self.msg("jailmgr.fs()", message=rootpath, log_level="INFO")
return rootpath
def kver(self):
"""
Returns the kernel version
"""
return "0.2.1-next1"
def netsock(self, ip, port, mode, msg):
"""
An easy interface to network sockets, built right into the jailmanager
Args:
ip: The IP of the server to access.
port: The port to access the server on
mode: Either UDP, TCP or PKG (HTTP)
msg: The message to send the server
Returns:
Whatever the server returns.
"""
raise NotImplementedError("TODO: Netsock will be implemented once 0.3.0 comes around!")
"""
This is a sort of OS built in Python, not bootable, but creates a custom directory structure and path definition.
This is the "kernel", it hosts all features and runs all programs.
For safety reasons the kernel is isolated, which means that with every shell instance,
A new kernel instance will follow it. Same goes for every program, it will need to call upon a brand-new kernel instance.
Version: 0.1.1-nps3
"""
import os
import runpy
class PyJail:
"""
The "kernel" for PyNVOS
"""
def __init__(self):
self.rootpath = ""
self.rootpath = self.fs()
def run_program(self, path_to_bin):
"""
Runs a specified program.
"""
path_to_bin = self.fs(path_to_bin)
# print(path_to_bin)
# print(str(self.rootpath) + str(path_to_bin))
if path_to_bin == 3 or path_to_bin == 2:
print("An error has occurred launching the program.")
else:
runpy.run_path(path_to_bin)
def fs(self, check_path=None) -> str:
"""
Keeps track of the jailed filesystem and makes sure any calls to any
file get done in the jailed filesystem
"""
if check_path is not None:
if os.path.exists(f"{self.rootpath}{check_path}"):
if check_path.startswith("."):
check_path = check_path.lstrip(".")
return os.getcwd() + check_path if "vfs" in os.getcwd() else 2
return self.rootpath + check_path
elif self.rootpath in check_path:
print("ERR: Cannot parse rootpath, expected vfspath")
return 3
else:
# Path is not in the jailed fs, so we say it doesn't exist.
print("ERR: File/directory doesn't exist in vfspath")
return 2
else:
rootpath = os.getcwd() + "/vfs"
return rootpath
@staticmethod
def is_posix_compatible() -> bool:
"""
Returns if the kernel is POSIX compatible.
"""
return False

40
sh.py
View File

@@ -1,60 +1,44 @@
"""
The shell for PyNVOS
Version: 0.2.0.0399
Version: 0.1.0-main1
"""
import importlib
import os
import cmd
import shutil
# from ..sys.krnl import Kernel
print(__name__)
class shell(cmd.Cmd):
jail_mgr = importlib.import_module(".jail_mgr", "vfs.sys")
jailmgr = jail_mgr.PyJail()
kver = jailmgr.kver()
intro = f"Shell started, PyNVOS {kver}"
prompt = "shell-0.2$ "
intro = "Shell started, PyNVOS 0.1.1-main1"
prompt = "shell-0.1.0$ "
file = None
krnl = importlib.import_module(".jail_mgr", "vfs.sys")
kernel = krnl.Kernel()
print(str(kernel) + " " + str(type(kernel)))
def do_cd(self, args):
"""Changes directory"""
args = shell.jailmgr.fs(args)
args = shell.kernel.fs(args)
os.chdir(args)
def do_exec(self, args):
"""Allows you to execute a file"""
# Apps in /bin should be allowed to launch without first adding /bin/ or ./, just the name of the executable
# So for ledit it should be just 'ledit' and not /bin/ledit.py or ./ledit.py
bins_in_bin = os.listdir(self.jailmgr.fs("/bin"))
bins_in_bin = os.listdir(shell.kernel.fs("/bin"))
apps_strip = []
for apps in bins_in_bin:
if apps.endswith(".py"):
apps_strip.append(apps.strip(".py"))
if args in apps_strip:
shell.jailmgr.run_program(f"/bin/{args}.py")
shell.kernel.run_program(f"/bin/{args}.py")
else:
shell.jailmgr.run_program(args)
shell.kernel.run_program(args)
def do_ls(self, none):
"""Lists the content of a directory"""
os.listdir(os.getcwd())
def do_pkg(self, pkg):
"""Downloads packages over the internet."""
self.jailmgr.netsock(f"https://pkg.novacow.ch/repo/{kver}/meta/{pkg}.pmd", 443, "PKG", "<null>")
shutil.copy(self.jailmgr.fs(f"/usr/netsock/cache/{pkg}.pmd"), self.jailmgr.fs(f"/usr/pkg/metacache/"))
with open(self.jailmgr.fs(f"/usr/pkgs/metacache/{pkg}.pmd"), "r") as f:
package_meta = f.read()
f.close()
print(package_meta)
y_n_confirmation = input("Do you want to install this package? [y/N] ")
if y_n_confirmation.lower() != "y":
print("Aborted.")
return
self.jailmgr.netsock(f"https://pkg.novacow.ch/repo/{kver}/main/static/binary/{pkg}.py", 443, "PKG", "<null>")
shutil.copy(self.jailmgr.fs(f"/usr/netsock/cache/{pkg}.py"), self.jailmgr.fs(f"/usr/bin/"))
def postloop(self):
pass
@@ -62,6 +46,6 @@ class shell(cmd.Cmd):
if __name__ == '<run_path>':
shell().cmdloop()
if __name__ == '__main__':
print("The shell can't be ran as a standalone program and must be ran in conjunction with the jail manager.")
print("The shell can't be ran as a standalone program and must be ran in conjunction with the kernel.")
input("Press Enter to continue...")
exit(-1)
exit(-1)