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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ for Linux, written on the kernel's own system-call interface.
openkal = "0.17.0"

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

## Why it does not use a C library
Expand Down
2 changes: 1 addition & 1 deletion mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
namespace = "mcpplibs"
name = "openkal-linux"
version = "0.18.2"
version = "0.18.3"
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
27 changes: 22 additions & 5 deletions src/fs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,25 @@ bool granted(preopen* t, kal_uintptr* n) {
return true;
}

// A DIRECTORY HANDLE NAMES A DIRECTORY; IT DOES NOT READ ONE (0.18.3).
//
// Every `kal_dir' is a base: names are resolved beneath it (`*at'), a program
// is started in it (`fchdir'), and it is described (`fstat', `fstatfs'). None
// of these reads the directory, and `O_PATH' is the open that asks for no
// access but naming. Reading happens only where entries are listed, and an
// enumeration already opens a descriptor of its own through the handle, with
// read access, at that moment.
//
// Until 0.18.2 the handles were opened for reading, and a directory the
// program may search but not read could not be a base at all. Measured on
// Android 14 under `run-as' (SELinux domain `runas_app', mcpp#857): the
// preopen of `/' failed with EACCES, so every name resolved beneath the root,
// and every program started by absolute name, failed before anything was
// started --- reported as "not found", because the base it would have been
// found in was never opened. The same program, its directory and its exec
// path all worked when each step was called directly.
constexpr okl_long dir_handle_flags = okl::o_path | okl::o_directory | okl::o_cloexec;

preopen* table(kal_uintptr* count) {
static preopen t[kMaxGrants];
static kal_uintptr n = 2;
Expand All @@ -102,11 +121,9 @@ preopen* table(kal_uintptr* count) {
else { g_cwd[0] = '.'; g_cwd[1] = '\0'; cwd_len = 1; }

const okl_long fd0 = okl::sys(okl::nr_openat, okl::at_fdcwd,
reinterpret_cast<okl_long>("."),
okl::o_rdonly | okl::o_directory | okl::o_cloexec, 0);
reinterpret_cast<okl_long>("."), dir_handle_flags, 0);
const okl_long fd1 = okl::sys(okl::nr_openat, okl::at_fdcwd,
reinterpret_cast<okl_long>("/"),
okl::o_rdonly | okl::o_directory | okl::o_cloexec, 0);
reinterpret_cast<okl_long>("/"), dir_handle_flags, 0);
t[0] = { g_cwd, cwd_len, okl::failed(fd0) ? 0u : okl::pack(static_cast<int>(fd0)) };
t[1] = { "/", 1, okl::failed(fd1) ? 0u : okl::pack(static_cast<int>(fd1)) };
}
Expand Down Expand Up @@ -398,7 +415,7 @@ int kal_fs_open_dir(kal_dir base, const char* name, kal_uintptr len, kal_dir* ou
if (b < 0 || out == nullptr || !okl::acceptable(name, len)) return kal_err_invalid;
okl::terminated t(name, len); if (!t.ok) return kal_err_invalid;
const okl_long fd = okl::sys(okl::nr_openat, b, reinterpret_cast<okl_long>(t.buf),
okl::o_rdonly | okl::o_directory | okl::o_cloexec, 0);
dir_handle_flags, 0);
if (okl::failed(fd)) return okl::translate(fd);
*out = kal_dir{ okl::pack(static_cast<int>(fd)) };
return kal_ok;
Expand Down
6 changes: 5 additions & 1 deletion src/process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,11 @@ inline okl_long handle_pid(kal_process h) { return static_cast<okl_long>(h.h & 0
// the duplicate still allocates nothing. It costs the property `execveat'
// bought, that the name is resolved through the descriptor, and is paid only
// where `execveat' is unusable: from the first start under a translator this
// implementation recognises, and after the first ENOSYS anywhere else.
// implementation recognises, and, elsewhere, by each start that meets ENOSYS,
// which falls back in the started image itself. That fallback is learned by
// no other start: the image is a copy made by `clone' without shared memory,
// so nothing it learns reaches this program, and the price is one refused
// system call per start.
//
// Ported from the copy mcppls carried (vendor/openkal-linux 0.15.1), which set
// the flag itself under PRoot; recognising the translator here removes that.
Expand Down
89 changes: 89 additions & 0 deletions tests/search_only_base.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// A directory the program may search but not read is still a base (0.18.3).
//
// A `kal_dir' names a directory; resolving beneath it, starting a program in
// it and describing it read nothing, so none of them needs the right to read
// it. Only listing does. Until 0.18.2 every handle was opened for reading, and
// on Android 14 under `run-as' (SELinux `runas_app') the root preopen itself
// failed with EACCES: every program started by absolute name was reported as
// not found (mcpp#857).
//
// Here the same situation is made with a mode instead of a policy: a directory
// of mode 0111. A privileged user reads it regardless, so the one assertion
// that it cannot be listed is made only for an unprivileged one.
#include <cstdio>
#include <cstring>
#include <sys/stat.h>
#include <unistd.h>
import openkal.fs;
import openkal.stream;
import openkal.types;
import openkal.process;

namespace {
int failures = 0;
void check(bool held, const char* what) {
if (!held) { std::printf("FAIL: %s\n", what); ++failures; }
}

kal_dir root() {
for (kal_uintptr i = 0; i < kal::fs::preopen_count(); ++i) {
kal_dir d{}; char name[512]; kal_uintptr len = 0;
if (kal_fs_preopen(i, &d, name, sizeof name, &len) == kal_ok && len == 1 && name[0] == '/')
return d;
}
return kal_dir{};
}
}

int main() {
const auto work = kal::fs::working();
check(kal_fs_mkdir(work, "okl_search_only", 15) == kal_ok, "a directory is created");
kal_dir d{};
check(kal_fs_open_dir(work, "okl_search_only", 15, &d) == kal_ok, "it opens");
kal_file f{};
check(kal::fs::open_file(d, "inside", 6, kal::fs::open::write | kal::fs::open::create, &f) == kal_ok,
"a file is created in it");
kal_stream_write(kal_fs_stream(f), "x", 1);
kal_fs_close_file(f);
kal_fs_close_dir(d);

check(::chmod("okl_search_only", 0111) == 0, "the directory loses its read right");

kal_dir s{};
check(kal_fs_open_dir(work, "okl_search_only", 15, &s) == kal_ok,
"a searchable directory without the read right opens as a base");
kal_file g{};
check(kal::fs::open_file(s, "inside", 6, kal::fs::open::read, &g) == kal_ok,
"a name beneath it resolves");
char c = 0;
check(kal_stream_read(kal_fs_stream(g), &c, 1) == 1 && c == 'x', "the file beneath it reads");
kal_fs_close_file(g);

// A program is started IN it: the base it is resolved under is the root
// preopen, and its working directory is the search-only directory.
const char* argv[] = { "true" };
const kal_uintptr lens[] = { 4 };
const kal_spawn how{ root(), s, nullptr, nullptr, 0, 0 };
kal_process p{};
const int rc = kal_process_spawn(&how, "bin/true", 8, argv, lens, 1, nullptr, nullptr, 0, nullptr, &p);
check(rc == kal_ok, "a program starts in a search-only directory");
if (rc == kal_ok) {
int status = -1, terminated = -1;
kal_process_wait(p, &status, &terminated);
kal_process_close(p);
check(terminated == 0 && status == 0, "the program ran to completion");
}

if (::geteuid() != 0) {
kal_uintptr iter = 0;
check(kal_fs_list_begin(s, &iter) == kal_err_permission,
"listing it is what the missing read right refuses");
}
kal_fs_close_dir(s);
::chmod("okl_search_only", 0755);
::unlink("okl_search_only/inside");
::rmdir("okl_search_only");

if (failures == 0) std::printf("search_only_base: ok\n");
return failures == 0 ? 0 : 1;
}
Loading