Skip to content
Open
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
143 changes: 104 additions & 39 deletions src/extension/mountinfo/mountinfo.c
Original file line number Diff line number Diff line change
@@ -1,9 +1,42 @@
#include "extension/extension.h"
#include "path/path.h" /* translate_path, */
#include "path/binding.h" /* Binding, bindings */
#include "path/temp.h" /* create_temp_file, */
#include <limits.h> /* INT_MAX, */
#include <linux/limits.h> /* PATH_MAX, */
#include <string.h> /* strlen, strcmp */
#include <stdio.h> /* FILE, getline, fprintf */
#include <stdlib.h> /* free, */
#include <sys/queue.h> /* CIRCLEQ_*, */

/**
* Append a synthesized mount-table line to @fp for each runtime
* binding (i.e. one that wasn't part of the static -r/-b set). This
* is what lets sandbox helpers like bubblewrap find the mount they
* just asked PRoot to create via emulate_mount().
*/
static void append_runtime_binding_lines(Tracee *target_tracee, FILE *fp)
{
Binding *binding;
int next_id = 1000000;
int parent_id = 1;

if (target_tracee->fs->bindings.guest == NULL)
return;

for (binding = CIRCLEQ_FIRST(target_tracee->fs->bindings.guest);
binding != (void *) target_tracee->fs->bindings.guest;
binding = CIRCLEQ_NEXT(binding, link.guest)) {
/* Skip the root binding "/" — already present as the kernel root. */
if (strcmp(binding->guest.path, "/") == 0)
continue;

fprintf(fp,
"%d %d 0:1 / %s rw,relatime - bind %s rw,relatime\n",
next_id++, parent_id,
binding->guest.path, binding->host.path);
}
}

static void mountinfo_check_open_path(Tracee *tracee, char path[PATH_MAX]) {
/* Try matching "/proc/<PID>/mountinfo" */
Expand Down Expand Up @@ -33,10 +66,29 @@ static void mountinfo_check_open_path(Tracee *tracee, char path[PATH_MAX]) {
char root_path[PATH_MAX]; // Host path to guest root
translate_path(target_tracee, root_path, AT_FDCWD, "/", true);
Comparison compare_result = compare_paths(root_path, "/data");
if (compare_result != PATH2_IS_PREFIX && compare_result != PATHS_ARE_EQUAL) {
return;
bool is_android_data = (compare_result == PATH2_IS_PREFIX || compare_result == PATHS_ARE_EQUAL);

/* Are there bindings to expose as fake mounts (mount(2)
* calls from sandbox helpers are converted into
* bindings — see emulate_mount). Skip the root
* binding, which the real kernel mount table already
* covers. */
bool has_extra_bindings = false;
if (target_tracee->fs->bindings.guest != NULL) {
Binding *b;
for (b = CIRCLEQ_FIRST(target_tracee->fs->bindings.guest);
b != (void *) target_tracee->fs->bindings.guest;
b = CIRCLEQ_NEXT(b, link.guest)) {
if (strcmp(b->guest.path, "/") != 0) {
has_extra_bindings = true;
break;
}
}
}

if (!is_android_data && !has_extra_bindings)
return;

/* Open real /proc/<PID>/mountinfo */
FILE *real_mountinfo_fp = fopen(path, "r");
if (real_mountinfo_fp == NULL) {
Expand All @@ -55,61 +107,74 @@ static void mountinfo_check_open_path(Tracee *tracee, char path[PATH_MAX]) {
size_t line_buf_len = 0;
ssize_t line_len = 0;
bool found_line = false;
while ((line_len = getline(&line, &line_buf_len, real_mountinfo_fp)) > 0) {
char *chunk = line;
/* Skip columns before 'root' */
for (int i = 0; i < 4 && chunk - line < line_len; i++) {
chunk = strchr(chunk, ' ');
if (chunk == NULL) goto end_line_scan;
chunk++;
}

/* Match path */
char *chunk_end = strchr(chunk, ' ');
if (chunk_end == NULL) continue;

if (chunk_end - chunk == 5 && 0 == memcmp(chunk, "/data", 5)) {
/* Write line into new file keeping only "/" from root column */
fwrite(line, chunk - line + 1, 1, new_mountinfo_fp);
fwrite(chunk_end, line_len - (chunk_end - line), 1, new_mountinfo_fp);
found_line = true;
break;
}
end_line_scan: ;
}

/* Once root was added, rescan and add other standard mounts */
if (found_line) {
fseek(real_mountinfo_fp, 0, SEEK_SET);
if (is_android_data) {
while ((line_len = getline(&line, &line_buf_len, real_mountinfo_fp)) > 0) {
char *chunk = line;
/* Skip columns before 'root' */
for (int i = 0; i < 4 && chunk - line < line_len; i++) {
chunk = strchr(chunk, ' ');
if (chunk == NULL) goto end_line_scan2;
if (chunk == NULL) goto end_line_scan;
chunk++;
}

/* Match path */
char *chunk_end = strchr(chunk, ' ');
if (chunk_end == NULL) continue;

size_t mount_len = chunk_end - chunk;
if (
(mount_len == 4 && 0 == memcmp(chunk, "/dev", 4)) ||
(mount_len >= 5 && 0 == memcmp(chunk, "/dev/", 5)) ||
(mount_len == 5 && 0 == memcmp(chunk, "/proc", 5)) ||
(mount_len == 4 && 0 == memcmp(chunk, "/sys", 4)) ||
(mount_len >= 5 && 0 == memcmp(chunk, "/sys/", 5)) ||
(mount_len == 4 && 0 == memcmp(chunk, "/tmp", 4))
) {
/* Copy line into new file verbatim */
fwrite(line, line_len, 1, new_mountinfo_fp);
if (chunk_end - chunk == 5 && 0 == memcmp(chunk, "/data", 5)) {
/* Write line into new file keeping only "/" from root column */
fwrite(line, chunk - line + 1, 1, new_mountinfo_fp);
fwrite(chunk_end, line_len - (chunk_end - line), 1, new_mountinfo_fp);
found_line = true;
break;
}
end_line_scan: ;
}

/* Once root was added, rescan and add other standard mounts */
if (found_line) {
fseek(real_mountinfo_fp, 0, SEEK_SET);
while ((line_len = getline(&line, &line_buf_len, real_mountinfo_fp)) > 0) {
char *chunk = line;
/* Skip columns before 'root' */
for (int i = 0; i < 4 && chunk - line < line_len; i++) {
chunk = strchr(chunk, ' ');
if (chunk == NULL) goto end_line_scan2;
chunk++;
}

/* Match path */
char *chunk_end = strchr(chunk, ' ');
if (chunk_end == NULL) continue;

size_t mount_len = chunk_end - chunk;
if (
(mount_len == 4 && 0 == memcmp(chunk, "/dev", 4)) ||
(mount_len >= 5 && 0 == memcmp(chunk, "/dev/", 5)) ||
(mount_len == 5 && 0 == memcmp(chunk, "/proc", 5)) ||
(mount_len == 4 && 0 == memcmp(chunk, "/sys", 4)) ||
(mount_len >= 5 && 0 == memcmp(chunk, "/sys/", 5)) ||
(mount_len == 4 && 0 == memcmp(chunk, "/tmp", 4))
) {
/* Copy line into new file verbatim */
fwrite(line, line_len, 1, new_mountinfo_fp);
}
end_line_scan2: ;
}
}
} else {
/* Non-Android case: copy real mountinfo verbatim. */
while ((line_len = getline(&line, &line_buf_len, real_mountinfo_fp)) > 0)
fwrite(line, line_len, 1, new_mountinfo_fp);
found_line = true;
}

/* Append synthesized entries for runtime bindings so
* helpers like bubblewrap find the mounts they think
* they just created. */
append_runtime_binding_lines(target_tracee, new_mountinfo_fp);

free(line);
fclose(new_mountinfo_fp);
fclose(real_mountinfo_fp);
Expand Down
76 changes: 49 additions & 27 deletions src/path/canon.c
Original file line number Diff line number Diff line change
Expand Up @@ -282,37 +282,59 @@ int canonicalize(Tracee *tracee, const char *user_path, bool deref_final,
/* It's a link, so we have to dereference *and*
* canonicalize to ensure we are not going outside the
* new root. */
comparison = compare_paths("/proc", guest_path);
switch (comparison) {
case PATHS_ARE_EQUAL:
case PATH1_IS_PREFIX:
/* Some links in "/proc" are generated
* dynamically by the kernel. PRoot has to
* emulate some of them. */
status = readlink_proc(tracee, scratch_path,
guest_path, component, comparison);
switch (status) {
case CANONICALIZE:
/* The symlink is already dereferenced,
* now canonicalize it. */
goto canon;

case DONT_CANONICALIZE:
/* If and only very final, this symlink
* shouldn't be dereferenced nor canonicalized. */
if (finality == FINAL_NORMAL) {
strcpy(guest_path, scratch_path);
return 0;
{
const char *proc_base = guest_path;
char alias_base[PATH_MAX];

comparison = compare_paths("/proc", guest_path);

/* If guest_path is not under /proc directly,
* check whether it aliases /proc via a binding
* (e.g. /oldroot/proc when /oldroot is bound to
* /). Otherwise links like /oldroot/proc/self
* would be resolved by the real kernel readlink
* and return PRoot's own pid. */
if (comparison != PATHS_ARE_EQUAL && comparison != PATH1_IS_PREFIX) {
strncpy(alias_base, guest_path, PATH_MAX - 1);
alias_base[PATH_MAX - 1] = '\0';
(void) substitute_binding(tracee, GUEST, alias_base);
if (strcmp(alias_base, guest_path) != 0) {
comparison = compare_paths("/proc", alias_base);
proc_base = alias_base;
}
}

switch (comparison) {
case PATHS_ARE_EQUAL:
case PATH1_IS_PREFIX:
/* Some links in "/proc" are generated
* dynamically by the kernel. PRoot has to
* emulate some of them. */
status = readlink_proc(tracee, scratch_path,
proc_base, component, comparison);
switch (status) {
case CANONICALIZE:
/* The symlink is already dereferenced,
* now canonicalize it. */
goto canon;

case DONT_CANONICALIZE:
/* If and only very final, this symlink
* shouldn't be dereferenced nor canonicalized. */
if (finality == FINAL_NORMAL) {
strcpy(guest_path, scratch_path);
return 0;
}
break;

default:
if (status < 0)
return status;
}
break;

default:
if (status < 0)
return status;
break;
}

default:
break;
}

status = readlink(host_path, scratch_path, sizeof(scratch_path));
Expand Down
17 changes: 12 additions & 5 deletions src/path/temp.c
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,18 @@ static int clean_temp_cwd()
|| strcmp(entry->d_name, "..") == 0)
continue;

status = chmod(entry->d_name, 0700);
if (status < 0) {
note(NULL, WARNING, SYSTEM, "cant chmod '%s'", entry->d_name);
nb_errors++;
continue;
/* Skip chmod on symlinks: chmod follows them and would
* report spurious errors when the target no longer
* exists (common with the /dev/{stdin,fd,...} symlinks
* bubblewrap leaves behind in emulated tmpfs dirs).
* We only need to unlink the symlink itself. */
if (entry->d_type != DT_LNK) {
status = chmod(entry->d_name, 0700);
if (status < 0) {
note(NULL, WARNING, SYSTEM, "cant chmod '%s'", entry->d_name);
nb_errors++;
continue;
}
}

if (entry->d_type == DT_DIR) {
Expand Down
Loading