Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 39 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ for Linux, written on the kernel's own system-call interface.

```toml
[dependencies]
openkal = "0.22.0"
openkal = "0.23.0"

[target.'cfg(os = "linux")'.dependencies]
openkal-linux = "0.23.0"
openkal-linux = "0.24.0"
```

## Why it does not use a C library
Expand Down Expand Up @@ -102,6 +102,43 @@ A program that carries a runtime of its own answers from that runtime instead
(`pthread_getattr_np`), because in that arrangement the runtime is what owns the
stacks and is what a program above openkal would ask for itself.

## What 0.24 adds (openkal 0.23)

**A region at an address.** `kal_exec_alloc_at` maps the region with
`MAP_FIXED_NOREPLACE`, which refuses rather than replaces what is there; a kernel
before 4.17 reads the flag as a hint, so a mapping placed elsewhere is released
and the request refused. `KAL_EXEC_PROP_PLACE` is claimed.

**A part sealed, a part read in.** `kal_exec_seal_part` is `mprotect` to read
only, and `mseal` for `KAL_EXEC_SEAL_FOREVER` where the kernel has it (6.10;
`KAL_EXEC_PROP_SEAL_FOREVER` is claimed by asking it once). `KAL_EXEC_MAP_PREFETCH`
is `MAP_POPULATE`; `KAL_EXEC_MAP_BTI` is `PROT_BTI` on arm64, claimed where the
kernel accepts it.

**A handle with less** (`kal_fs_dir_attenuate`): a table indexed by descriptor,
consulted by every operation of `openkal.fs` that takes a directory and empty in
a program that attenuates nothing. A read-only handle refuses every change; one
that states nothing changes answers absence from a listing it has read, within
a budget the resource preference sets; a recorded one appends `<kind><name>\0`
to its record in batches, and `e\0` at `kal_exit` or the C library's
destructors. A grant carries its rights in `KAL_GRANT_RIGHTS`, and its record
as a descriptor after the grants.

**Confinement** (`KAL_SPAWN_CONFINE`, `KAL_PROCESS_PROP_CONFINE`): Landlock, at
the ABI version the kernel reports, set in the started image before it is
replaced. The grants are the rules, by their rights; the program's own file,
`/proc` and `/dev/null` are added, and `PR_SET_NO_NEW_PRIVS` is set.

**Observation.** A fault is reported on an alternate stack from
`/proc/self/maps` and the ELF symbol tables, and the program is then ended as
the kernel ends it. Samples are `ITIMER_PROF` and `SIGPROF` into a ring a
handler writes without a lock. Touches: a part recorded is made readable and
not executable, and the handler makes each page executable again at its first
execution and records it; a fault that is not a touch is reported, or passed
to the program's own handler. `kal_process_info` reports minor and major
faults (`getrusage`, `/proc/<pid>/stat`). `kal_memory_info` is `MemAvailable`
within the cgroup's `memory.max`; economy sets `PR_SET_THP_DISABLE`.

## What 0.23 adds (openkal 0.22)

**The null device at a position.** `KAL_SPAWN_NULL_IN`, `KAL_SPAWN_NULL_OUT` and
Expand Down
6 changes: 4 additions & 2 deletions mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
namespace = "mcpplibs"
name = "openkal-linux"
version = "0.23.0"
version = "0.24.0"
description = "The reference implementation of openkal for Linux, written on the kernel's own system-call interface so that it can be placed beneath a C library as well as above one."
license = "Apache-2.0"

Expand Down Expand Up @@ -62,10 +62,12 @@ provides-interfaces = [
"openkal.timeout",
"openkal.map",
"openkal.region",
"openkal.prof",
"openkal.debug",
]

[dependencies]
openkal = "0.22.0"
openkal = "0.23.0"

# The package contributes definitions and no modules. The interface it
# implements is declared by the specification package, which this package
Expand Down
4 changes: 4 additions & 0 deletions src/abort.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "sys.h"
#include "rights.h"
#include <openkal/abort.h>

extern "C" {
Expand All @@ -20,6 +21,9 @@ extern "C" {
// destructors first --- is not observable in a small program and is observable
// in a large one, which is why it is stated rather than left to judgement.
[[noreturn]] void kal_exit(int code) {
// What the program's attenuated handles recorded is written, and ended
// (0.23, fs.h): a record without its end is of a program that did not end.
okl::rights::end_of_program();
for (;;) okl::sys(okl::nr_exit_group, code);
}

Expand Down
42 changes: 42 additions & 0 deletions src/env.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "sys.h"
#include <openkal/env.h>

namespace okl { int resource_preference(); }
namespace okl {

// The vectors the kernel places on the stack at inception. They are recorded
Expand Down Expand Up @@ -252,4 +253,45 @@ kal_intptr kal_env_system(char* out, kal_uintptr cap) {
return give("linux", 5, out, cap);
}

// THE RESOURCE PREFERENCE (0.23), which a start conveys in KAL_RESOURCES
// (process.cpp) and a person sets the same way.
int kal_env_resources(void) { return okl::resource_preference(); }

}

namespace okl {
int resource_preference() {
static int known = -1;
int v = __atomic_load_n(&known, __ATOMIC_RELAXED);
if (v >= 0) return v;
v = 0;
constexpr char key[] = "KAL_RESOURCES=";
constexpr okl_uptr key_len = sizeof key - 1;
for (char** e = okl::g_envp; e && *e; ++e) {
okl_uptr i = 0;
while (i < key_len && (*e)[i] == key[i]) ++i;
if (i != key_len) continue;
const char* val = *e + key_len;
auto is = [&](const char* w) {
okl_uptr k = 0;
while (w[k] && val[k] == w[k]) ++k;
return w[k] == '\0' && val[k] == '\0';
};
v = is("performance") ? 1 : is("economy") ? 2 : 0;
break;
}
__atomic_store_n(&known, v, __ATOMIC_RELAXED);
return v;
}
} // namespace okl

namespace {
// THE ENVIRONMENT'S OWN POLICY FOR MEMORY, AS THE PREFERENCE ASKS (0.23): a
// program started for economy has no transparent huge pages, which hold a whole
// two mebibytes resident for the first byte touched. After `capture', which
// reads the environment.
[[gnu::constructor(102)]] void apply_resource_preference() {
if (okl::resource_preference() == 2)
okl::sys(okl::nr_prctl, 41 /* PR_SET_THP_DISABLE */, 1, 0, 0, 0);
}
} // namespace
78 changes: 74 additions & 4 deletions src/exec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,25 @@ okl_uptr round_up(okl_uptr n, okl_uptr to) { return (n + to - 1) & ~(to - 1); }

} // namespace

namespace {
// Whether this kernel has `mseal': it answers a range of no length with zero,
// and a kernel without it ENOSYS.
bool sealing() {
static int known = -1;
if (known < 0) known = okl::failed(okl::sys(okl::nr_mseal, 0, 0, 0)) ? 0 : 1;
return known == 1;
}
// Whether instructions mapped PROT_BTI are held to branch targets: the
// processor states it in AT_HWCAP2 (HWCAP2_BTI), and only on aarch64.
bool branch_targets() {
#if defined(__aarch64__)
return (okl::auxval(26 /* AT_HWCAP2 */) & (okl_ulong{1} << 17)) != 0;
#else
return false;
#endif
}
} // namespace

extern "C" {

void* kal_exec_alloc(kal_uintptr size) {
Expand All @@ -51,6 +70,26 @@ void* kal_exec_alloc(kal_uintptr size) {
return reinterpret_cast<void*>(r);
}

// AT AN ADDRESS (0.23): MAP_FIXED_NOREPLACE, which refuses rather than replaces
// what is there. A kernel before 4.17 does not know the flag and reads the
// address as a hint, so the answer is compared and a mapping placed elsewhere
// released: the address is either given or refused, never approximated.
void* kal_exec_alloc_at(void* at, kal_uintptr size) {
const okl_uptr where = reinterpret_cast<okl_uptr>(at);
if (size == 0 || where == 0 || (where & (page() - 1)) != 0) return nullptr;
const okl_uptr bytes = round_up(static_cast<okl_uptr>(size), page());
const okl_long r = okl::sys(okl::nr_mmap, static_cast<okl_long>(where), static_cast<okl_long>(bytes),
okl::prot_read | okl::prot_write,
okl::map_private | okl::map_anonymous | 0x100000 /* MAP_FIXED_NOREPLACE */,
-1, 0);
if (okl::failed(r)) return nullptr;
if (static_cast<okl_uptr>(r) != where) {
okl::sys(okl::nr_munmap, r, static_cast<okl_long>(bytes));
return nullptr;
}
return at;
}

int kal_exec_publish(void* p, kal_uintptr size) {
if (p == nullptr || size == 0) return kal_err_invalid;
const okl_uptr bytes = round_up(static_cast<okl_uptr>(size), page());
Expand Down Expand Up @@ -114,6 +153,25 @@ int kal_exec_publish_part(void* region, kal_uintptr offset, kal_uintptr size) {
return okl::failed(r) ? okl::translate(r) : kal_ok;
}

// SEALED READ-ONLY (0.23): the protection, and, for ever, `mseal' (Linux 6.10)
// over it, after which the kernel refuses to change the part's protection or to
// unmap it --- `mprotect' and `munmap' of it report EPERM, which this
// implementation reports as kal_err_permission.
int kal_exec_seal_part(void* region, kal_uintptr offset, kal_uintptr size, kal_uintptr flags) {
const okl_uptr g = page();
if (region == nullptr || size == 0 || (offset & (g - 1)) != 0 || (size & (g - 1)) != 0
|| (reinterpret_cast<okl_uptr>(region) & (g - 1)) != 0 || (flags & ~KAL_EXEC_SEAL_FOREVER) != 0)
return kal_err_invalid;
const okl_long at = reinterpret_cast<okl_long>(static_cast<char*>(region) + offset);
const okl_long r = okl::sys(okl::nr_mprotect, at, static_cast<okl_long>(size), okl::prot_read);
if (okl::failed(r)) return okl::translate(r);
if ((flags & KAL_EXEC_SEAL_FOREVER) && sealing()) {
const okl_long m = okl::sys(okl::nr_mseal, at, static_cast<okl_long>(size), 0);
if (okl::failed(m)) return okl::translate(m);
}
return kal_ok;
}

kal_uintptr kal_exec_granularity(void) { return page(); }

// A PART THAT IS A FILE'S (0.21): the file's pages mapped over the part,
Expand All @@ -127,18 +185,29 @@ int kal_exec_map_part(void* region, kal_uintptr offset, kal_uintptr size, kal_fi
const int fd = okl::unpack(f.h);
if (region == nullptr || size == 0 || fd < 0 || (offset & (g - 1)) != 0 || (size & (g - 1)) != 0
|| (reinterpret_cast<okl_uptr>(region) & (g - 1)) != 0 || at % KAL_MAP_ALIGNMENT != 0
|| (how & ~KAL_EXEC_MAP_EXECUTE) != 0)
|| (how & ~(KAL_EXEC_MAP_EXECUTE | KAL_EXEC_MAP_PREFETCH | KAL_EXEC_MAP_BTI | KAL_EXEC_MAP_WRITE)) != 0
|| ((how & KAL_EXEC_MAP_WRITE) && (how & KAL_EXEC_MAP_EXECUTE)))
return kal_err_invalid;
okl::kstat st{};
const okl_long s = okl::sys(okl::nr_fstat, fd, reinterpret_cast<okl_long>(&st));
if (okl::failed(s)) return okl::translate(s);
if ((st.mode & okl::s_ifmt) != okl::s_ifreg) return kal_err_not_supported;
const kal_u64 length = st.size > 0 ? static_cast<kal_u64>(st.size) : 0;
if (at > length || static_cast<kal_u64>(size) > length - at) return kal_err_invalid;
const okl_long prot = okl::prot_read | ((how & KAL_EXEC_MAP_EXECUTE) ? okl::prot_exec : 0);
// READ IN NOW (0.23): MAP_POPULATE reads the part and fills its page
// tables in the one call, where each page would otherwise be a fault of its
// own on its first touch. BRANCH TARGETS: PROT_BTI, where the processor
// holds them.
const bool bti = (how & KAL_EXEC_MAP_BTI) && (how & KAL_EXEC_MAP_EXECUTE) && branch_targets();
// WRITTEN PRIVATELY (0.23): the mapping is private already, so a page the
// program writes is its own copy and the file is not changed.
const okl_long prot = okl::prot_read | ((how & KAL_EXEC_MAP_EXECUTE) ? okl::prot_exec : 0)
| ((how & KAL_EXEC_MAP_WRITE) ? okl::prot_write : 0)
| (bti ? 0x10 /* PROT_BTI */ : 0);
const okl_long populate = (how & KAL_EXEC_MAP_PREFETCH) ? 0x8000 /* MAP_POPULATE */ : 0;
const okl_long r = okl::sys(okl::nr_mmap,
reinterpret_cast<okl_long>(static_cast<char*>(region) + offset),
static_cast<okl_long>(size), prot, okl::map_private | okl::map_fixed, fd,
static_cast<okl_long>(size), prot, okl::map_private | okl::map_fixed | populate, fd,
static_cast<okl_long>(at));
if (okl::failed(r)) {
// ENODEV: the file system does not map. EPERM: its files are not
Expand All @@ -163,7 +232,8 @@ void kal_exec_free(void* p, kal_uintptr size) {
kal_uintptr kal_exec_props(void) {
// Executable memory is available to every artifact on this kernel: nothing
// here is granted only to a program produced in a particular way.
return KAL_EXEC_PROP_REPUBLISH | KAL_EXEC_PROP_AVAILABLE | KAL_EXEC_PROP_FILE;
return KAL_EXEC_PROP_REPUBLISH | KAL_EXEC_PROP_AVAILABLE | KAL_EXEC_PROP_FILE | KAL_EXEC_PROP_PLACE
| (sealing() ? KAL_EXEC_PROP_SEAL_FOREVER : 0u) | (branch_targets() ? KAL_EXEC_PROP_BTI : 0u);
}

} // extern "C"
Loading
Loading