Skip to content

Instantly share code, notes, and snippets.

@Kilobyte22
Last active August 29, 2015 14:04
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Kilobyte22/78473433c969eb671998 to your computer and use it in GitHub Desktop.
Save Kilobyte22/78473433c969eb671998 to your computer and use it in GitHub Desktop.

Abstract

This document works as draft for the OpenPosix standard, version 1.0. It is a standard for operating systems for the Minecraft mod OpenComputers. The aim is to archieve maximum compatibility while providing a Unix style theme.

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.

Processes

Operating systems following that standard MUST support multiple processes running at the same time. A process is accociated with an environment, an id, a current working directory, 3 user ids (effective, real and saved) and 3 group ids (also effective, real and saved). Processes are seperated for the most part even though there are some ways for processes to interact

The PID

The PID (Process IDentifier) is a system wide unique handle for the process. The first process (init) gets the ID 1. After that the id gets increased for each process. If a process exits its id will not get reused to avoid confusion.

The Environment

The environment is basicly the memory a process can access, also known as _G. If the OS supports multithreading, there is also a _T, providing thread-specific memory (a metatable can be used to archieve this). A metatable on _G and _ENV ensures that only the right process can access data, even if they share the same environment (for example after a use of the spawn() syscall)

The working directory

The working directory is where the process resides. Any relative file names used will be applied relative to this directory.

User and Group IDs

The user and group ids specify the processes owner. You can change any of them, but only to values that are already one of them. For example a process has these uids: 2, 3, 4 (e, r, s). It can now change any of them to 2, 3 or 4. Exception to this rule: a process can arbritarily set any UIDs/GIDs if the process has the capability for it. Note that, if the system does not enforce user/group policies, it always has to assume UID/GID 0 (root)

Effective

This is the UID/GID used to check for permissions. Will be same as real UID/GID unless manually set or the executable has the setuid/setgid bit set

Real

This is the UID/GID of the actual person executing

Saved

This one defaults to the real UID/GID. Its only purpose is to being able to temporarily change your own effective UID/GID

Syscalls

Syscalls are the way of a process to send messages to the kernel. The RECOMMENDED way for doing so is by internally making syscall() call coroutine.yield(). syscall is a table with __call metamethod, making syscall("mycall") and syscall.mycall() equivalent. The metatable SHOULD be added as wrapper in the usermode initializer (a script that runs in a usermode _G before a process is executed).

*(): table[string]

The * syscall returns a list of all valid syscalls. It is mainly to be used internally.

invoke(fd: number, function: string, ...)

Invokes a call for a file descriptor. Normal files have write(data: string), read(count: number), seek(pos: number) and close(). read will read at least 1 char, unless it reaches the end of the file; If count is nil it reads until the end of the file descriptor (if supported. A socket for example which does not support this should return nil and and error message). It is a blocking call. all of them return false, error code, human readable message on error, all but read true on success. read returns a string (possibly empty) on success

chdir(dir: string): boolean[, number, string]

changes current working directory. Does take relative directory names into account.

pwd(): string

gets current working directory

lsdir(dir: string): (table|nil, number, string)

Lists all files in a directory. If this fails, it returns nil, followed by an error code and a human readable error message

open(file: string, mode: string): (number|nil, number, string)

opens a file, returning a file descriptor

socket(): number

Creates a new socket fd. It initially does nothing. Following methods are available through invoke():

socket.connect(host: string, port: number): boolean[, number, string]

Connects to a remote host. This can be both another OC computer or a real world machine.

socket.listen([ifaces: table[string]], port: number): boolean[, number, string]

Makes this socket a listen socket. If no interface list is provided it defaults to all supporting interfaces. If a not supporting interfact (for example an Internet Card) is in the interface list, an error will be returned

socket.accept(): (fd|nil, number, string)

Blocks until a new incoming request is served. Then returns a file descriptor to it

socket.sent(host: string, port: number): boolean[, number, string]

Send a Datagram (UDP style) packet

socket.bind([ifaces: table[string]], port: number): boolean[, number, string]

after this, the socket can accept UDP packets (to be specified)

pipe(): number, number

Creates a Unix pipe, where all data written to the first FD can be read from second

ps(): table[table(id: number, name: string, uid: number, gid: number, parent: number, tty: string)]

Returns a list of all running processes

who(): table[table(uid: number, tty: string)]

Returns a list of all logged in users, including their tty. a user can be listed multiple times.

chroot(dir: string): boolean[, number, string]

Jails the current process into a certain directory

hasUserCapability(uid: number, capability: string): boolean

checks if a user has a capability. User id must always be the one of current process unless user has capability to bypass (both effective and real work)

hasProcessCapability(pid: number, capability: string): boolean

checks if a process has a capability. The euid of selected process must be same as either ruid or euid of current proces or bypass capability must be granted

setProcesCapability(pid: number, capability: string, value: boolean|nil)

Sets a processes capability. Needs writeAllCapab capability. If value is not nil it overrides the value for the user

setUserCapability(uid: number, capability: string, value: boolean)

Sets a users capabilities. Needs writeAllCapab capability.

mount(driver: string, device: (number|usernil), mountpoint: string, options: table): boolean[, number, string]

Mounts [device] using [driver] in [mountpoint]
driver must be a valid kernel mode driver. device a file descriptor to the device file. if the fd is opened in read mode, the device can only be mounted with the 'ro' option. it can be nil if there is no device (like for a tempfs). mountpoint has to be an existing empty directory with rwx permissions all set. Options can be arbitrary: the following ones exist by default:

shutdown()

issues a shutdown by sending the 'shutdown' ipc call to init

reboot()

issues a reboot by sending the 'reboot' ipc call to init

rw: boolean - default: true, conflicts with ro - device mounted read-write
ro: boolean - default: false, conflicts with rw - device mounted readonly
user: boolean - (fstab only) default: false - mounting can be done by a user without need for the mount capability

checkPermission(file: string, permission: string): boolean

Checks a file permission. permission is a string consisting of any combination of r w and x (read, write and execute)

symlink(source: string, target: string): boolean[, number, string]

Creates a symbolic link. A symbolic link is basicly an alias for a file or directory. they act transparent, so if you open a symbolic link it behaves exactly as if you had opened the target. If the target does not exist it should instead error as if the link itself did not exist. The system may however replace the human readable error message with something like "<source>: invalid symbolic link to <target>". This feature is not mandatory but it SHOULD be implemented by all main file systems. Symlinks can go across different mount points. They must be stored to the physical file system.

link(source: string, target: string): boolean[, number, string]

Creates a hard link. A hard link nothing but a second file name for a file or directory. Once created it makes no difference if you access the source or target. They behave exactly same. In fact, after deletion of the source you can still access the target. This is archieved by each file having a reference count. This starts out with 1. Each creation of a hard link increments it. Each deletion of a hard link decrements it (the original file name is just a hard link as well). When the counter reaches 0 the actual file gets removed. Hard links do not work cross device. Creation of directory hard links requires the linkDirectory capability. implementation of hard links is OPTIONAL. If implemented, they must be stored to the physical file system.

fileinfo(file: string): (table|nil, number, string)

Retrieves information on a file or directory. Table entries:

  • type: string - one of file, directory, device, fifo, mount and link
  • target: string|nil - symbolic link only: the link target
  • mode: number - the file mode. a 3 digit number. See file permissions section
  • owner: number - the UID of the owner
  • group: number - the GID of owning group
  • size: number - file only: the size in bytes
  • refcount: number - the reference counter used for hard links

mkfifo(file: name): boolean[, number, string]

makes a new fifo (named pipe). A named pipe can be opened by one process for reading and for writing by another. all data written to it can then be read again.

File Permissions

Each file has a permission mask. 000 means no access, 777 means full access for all users. The first digit represents owner, second means owner group and last means everything else. The digit is a bitmask with the following values: execute: 1, write: 2, execute: 4. Execute on directories means that you can chdir() to that directory

Capabilities

Capabilities are general purpose permissions. In fact a kernel module can define custom ones. By default root has all capabilities and every other user has none (this can be changed though). A user with all capabilities should not behave any different than root and a if you take all capabilities from root it should not be have any different than a regular user

readAllFiles      - allows to read all files on the system bypassing permission checks
writeAllFiles     - allows to change all files on the system (dangerous)
chmodAllFiles     - allows to change owner and mode of any file in the system (dangerous)
readAllCapab      - allows to read capabilities of all processes/users
writeAllCapab     - allows to change any users capabilities (dangerous)
changeToAnyUID    - allows to change current processes user ids to any valid user id (dangerous) 
changeToAnyGID    - allows to change current processes group ids to any valid group id (dangerous)
shutdown          - allows to shut down or reboot the system
linkDirectory     - allows to create hard links to directories

A program that needs extended privileges should never check if it is run as root. instead it should check if it has the needed capabilities

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment