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
2 changes: 2 additions & 0 deletions cmd/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,8 @@ snap_confine_snap_confine_SOURCES = \
snap-confine/cookie-support.h \
snap-confine/mount-support-nvidia.c \
snap-confine/mount-support-nvidia.h \
snap-confine/mount-support-hybris.c \
snap-confine/mount-support-hybris.h \
snap-confine/mount-support.c \
snap-confine/mount-support.h \
snap-confine/ns-support.c \
Expand Down
238 changes: 238 additions & 0 deletions cmd/snap-confine/mount-support-hybris.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
/*
* Copyright (C) 2025 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

#include "mount-support-hybris.h"
#include "config.h"

#include <errno.h>
#include <fcntl.h>
#include <glob.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
/* POSIX version of basename() and dirname() */
#include <libgen.h>

#include "../libsnap-confine-private/classic.h"
#include "../libsnap-confine-private/cleanup-funcs.h"
#include "../libsnap-confine-private/string-utils.h"
#include "../libsnap-confine-private/utils.h"
#include "mount-support.h"

#define SC_HYBRIS_ROOTFS "/android"

#define SC_HYBRIS_SYSTEM_SYMLINK "/system"
#define SC_HYBRIS_VENDOR_SYMLINK "/vendor"
#define SC_HYBRIS_ODM_SYMLINK "/odm"
#define SC_HYBRIS_APEX_SYMLINK "/apex"
#define SC_HYBRIS_LINKERCONFIG_SYMLINK "/linkerconfig"

#define SC_HYBRIS_SYSTEM_SYMLINK_TARGET "/android/system"
#define SC_HYBRIS_VENDOR_SYMLINK_TARGET "/android/vendor"
#define SC_HYBRIS_ODM_SYMLINK_TARGET "/android/odm"
#define SC_HYBRIS_APEX_SYMLINK_TARGET "/android/apex"
#define SC_HYBRIS_LINKERCONFIG_SYMLINK_TARGET "/android/linkerconfig"

static void sc_hybris_mount_android_rootfs(const char *rootfs_dir) {
// Bind mount the Halium rootfs as set up by the host, in /android
char path_buf[PATH_MAX] = {0};
sc_must_snprintf(path_buf, sizeof(path_buf), "%s%s", rootfs_dir, SC_HYBRIS_ROOTFS);
const char *android_rootfs_dir = path_buf;

int res = mkdir(android_rootfs_dir, 0755);
if (res != 0 && errno != EEXIST) {
die("cannot create bind-mount target %s", android_rootfs_dir);
}
if (res == 0 && (chown(android_rootfs_dir, 0, 0) < 0)) {
// Adjust the ownership only if we created the directory.
die("cannot change ownership of %s", android_rootfs_dir);
}

if (mount(SC_HYBRIS_ROOTFS, android_rootfs_dir, NULL, MS_BIND | MS_REC | MS_RDONLY, NULL)) {
die("Cannot mount Halium environment into target");
}

sc_must_snprintf(path_buf, sizeof(path_buf), "%s%s", rootfs_dir, SC_HYBRIS_SYSTEM_SYMLINK);
const char *android_system_symlink = path_buf;
if (symlink(SC_HYBRIS_SYSTEM_SYMLINK_TARGET, android_system_symlink)) {
die("Cannot set symlink for %s", SC_HYBRIS_SYSTEM_SYMLINK);
}

sc_must_snprintf(path_buf, sizeof(path_buf), "%s%s", rootfs_dir, SC_HYBRIS_VENDOR_SYMLINK);
const char *android_vendor_symlink = path_buf;
if (symlink(SC_HYBRIS_VENDOR_SYMLINK_TARGET, android_vendor_symlink)) {
die("Cannot set symlink for %s", SC_HYBRIS_VENDOR_SYMLINK);
}

sc_must_snprintf(path_buf, sizeof(path_buf), "%s%s", rootfs_dir, SC_HYBRIS_ODM_SYMLINK);
const char *android_odm_symlink = path_buf;
if (symlink(SC_HYBRIS_ODM_SYMLINK_TARGET, android_odm_symlink)) {
die("Cannot set symlink for %s", SC_HYBRIS_ODM_SYMLINK);
}

sc_must_snprintf(path_buf, sizeof(path_buf), "%s%s", rootfs_dir, SC_HYBRIS_APEX_SYMLINK);
const char *android_apex_symlink = path_buf;
if (symlink(SC_HYBRIS_APEX_SYMLINK_TARGET, android_apex_symlink)) {
die("Cannot set symlink for %s", SC_HYBRIS_APEX_SYMLINK);
}

sc_must_snprintf(path_buf, sizeof(path_buf), "%s%s", rootfs_dir, SC_HYBRIS_LINKERCONFIG_SYMLINK);
const char *android_linkerconfig_symlink = path_buf;
if (symlink(SC_HYBRIS_LINKERCONFIG_SYMLINK_TARGET, android_linkerconfig_symlink)) {
die("Cannot set symlink for %s", SC_HYBRIS_LINKERCONFIG_SYMLINK);
}
}

static bool has_mounted_halium_path(const char *path) {
// Halium system image existance
FILE *mounts = NULL;
char *line = NULL;
int character = 0;
unsigned int i = 0;
unsigned int line_size = 0;
static const unsigned int line_increase = 64;
bool found = false;

// Check whether the Halium system image has been mounted.
mounts = fopen("/proc/mounts", "r");
if (!mounts) {
return false;
}

line = (char *)malloc(sizeof(char) * line_increase);
if (!line) {
fclose(mounts);
return false;
}

line_size = line_increase;
line[0] = '\0';

// Files in /proc don't expose their size, so read into a buffer character-by-character.
while ((character = fgetc(mounts)) != EOF) {
if (i >= line_size) {
line = (char *)realloc(line, sizeof(char) * (line_size + line_increase));
if (!line) {
fclose(mounts);
return false;
}

line_size += line_increase;
line[line_size - 1] = '\0';
}

line[i] = (char)character;

if (line[i] == '\n') line[i] = '\0';

// Judge the line we read now
if (line[i] == '\0') {
// These must exist on disk, not on some tmpfs,
// using filesystems prominently used on Android.
if ((strncmp(line, " ext4 ", 6) == 0 || strncmp(line, " f2fs ", 6) == 0) &&
strncmp(line, path, strlen(path)) == 0) {
found = true;
break;
}

line[0] = '\0';
i = 0;
} else {
++i;
}
}

fclose(mounts);
free(line);
return found;
}

int sc_mount_is_halium_system(void) {
// Halium-typical paths to check for
// snapd's "opengl" interface takes care of exposing it all
// to the confined environment
static const char *halium_paths[] = {
"/system/build.prop",
#ifdef __LP64__
"/system/lib64/libEGL.so",
#else
"/system/lib/libEGL.so"
#endif
};
static const char *halium_mountpoints[] = {
" /android/vendor ",
" /android/data ",
" /android/cache ",
};
static const char *halium_symlinks[] = {"/system", "/vendor", "/odm", "/data"};
static const char *binder_paths[] = {"/dev/binderfs/binder", "/dev/binderfs/hwbinder", "/dev/binder",
"/dev/hwbinder"};

// Check if this is running on a system with binder devices, which all Halium systems do.
bool has_binder = false;
for (long unsigned int i = 0; i < sizeof(binder_paths) / sizeof(binder_paths[0]); i++) {
struct stat info;
if (stat(binder_paths[i], &info) == 0) {
has_binder = true;
break;
}
}
if (!has_binder) {
return 0;
}

// These are required mountpoints which are used by our Halium LXC container
for (long unsigned int i = 0; i < sizeof(halium_mountpoints) / sizeof(halium_mountpoints[0]); i++) {
if (!has_mounted_halium_path(halium_mountpoints[i])) {
return 0;
}
}

// Next check for commonly required host-side files we want to pass
for (long unsigned int i = 0; i < sizeof(halium_paths) / sizeof(halium_paths[0]); i++) {
struct stat info;
if (stat(halium_paths[i], &info) != 0) {
return 0;
}
}

// These symlinks must exist in GNU/Linux Land for hybris to work
for (long unsigned int i = 0; i < sizeof(halium_symlinks) / sizeof(halium_symlinks[0]); i++) {
struct stat info;
if (lstat(halium_symlinks[i], &info) != 0) {
return 0;
}
}

return 1;
}

void sc_mount_hybris_driver(const char *rootfs_dir, const char *base_snap_name) {
// Only proceed if this has been identified as a Halium system, on Ubuntu Touch
// we require access to a lot of unvetted libraries and unmediated IPC, and
// misc required files. No executable bits allowed, this is the most fine-grained
// while future-resiliant as patched-in, shipped and used set of rules and requirements.
if (!sc_mount_is_halium_system()) {
return;
}

sc_hybris_mount_android_rootfs(rootfs_dir);
}
47 changes: 47 additions & 0 deletions cmd/snap-confine/mount-support-hybris.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (C) 2025 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

#ifndef SNAP_CONFINE_MOUNT_SUPPORT_HYBRIS_H
#define SNAP_CONFINE_MOUNT_SUPPORT_HYBRIS_H

/**
* Check whether the running system looks like a regular Halium distribution
*
* Distributions using Halium have an Android Generic System Image mounted at /android,
* with symlinks pointing to various Android-typical directories, like /system & /vendor,
* and they make extensive use of Binder IPC.
*
* Verify this system's environment matches expectations of a Halium system and return.
**/
int sc_mount_is_halium_system(void);

/**
* Make the libhybris drivers from the classic distribution available in the snap
* execution environment.
*
* libhybris allows for ABI guarantees as long as their wrappers can be linked or
* dlopen()'ed because it is the library loader, it resolves the symbols and links them.
* /android needs to live inside the Snap environment too for the actual bionic-built
* libraries to be found, loaded and their functions executed.
*
* /android and the respective compatibility symlinks from /system to /android/system
* etc. allow for loading the appropriate userspace components for proper use
* (assuming AppArmor plays along).
**/
void sc_mount_hybris_driver(const char *rootfs_dir, const char *base_snap_name);

#endif
2 changes: 2 additions & 0 deletions cmd/snap-confine/mount-support-test.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
*/

#include "mount-support.h"
#include "mount-support-hybris.c"
#include "mount-support-hybris.h"
#include "mount-support-nvidia.c"
#include "mount-support-nvidia.h"
#include "mount-support.c"
Expand Down
2 changes: 2 additions & 0 deletions cmd/snap-confine/mount-support.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
#include "../libsnap-confine-private/string-utils.h"
#include "../libsnap-confine-private/tool.h"
#include "../libsnap-confine-private/utils.h"
#include "mount-support-hybris.h"
#include "mount-support-nvidia.h"

#define MAX_BUF 1000
Expand Down Expand Up @@ -753,6 +754,7 @@ static void sc_bootstrap_mount_namespace(const struct sc_mount_config *config) {
// pre-pivot filesystem.
if (config->distro == SC_DISTRO_CLASSIC) {
sc_mount_nvidia_driver(scratch_dir, config->base_snap_name);
sc_mount_hybris_driver(scratch_dir, config->base_snap_name);
}
// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
// pivot_root
Expand Down
7 changes: 7 additions & 0 deletions cmd/snap-confine/snap-confine.apparmor.in
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ profile @LIBEXECDIR@/snap-confine flags=(attach_disconnected) {
/sys/kernel/mm/transparent_hugepage/enabled r,
/sys/kernel/mm/transparent_hugepage/hpage_pmd_size r,

# mount-support-hybris: Checks for vendor-provided partitions
# to ensure additional paths are only mounted on Halium systems
@{PROC}/{,self/,@{pid}/}mounts r,

# cgroup: reading own cgroup
@{PROC}/@{pid}/cgroup r,

Expand Down Expand Up @@ -270,6 +274,9 @@ profile @LIBEXECDIR@/snap-confine flags=(attach_disconnected) {
mount options=(rw rbind) /mnt/ -> /tmp/snap.rootfs_*/mnt/,
mount options=(rw rslave) -> /tmp/snap.rootfs_*/mnt/,

mount options=(ro rbind) /android/ -> /tmp/snap.rootfs_*/android/,
mount options=(ro rslave) -> /tmp/snap.rootfs_*/android/,

# allow making host snap-exec available inside base snaps
mount options=(rw bind) @LIBEXECDIR@/ -> /tmp/snap.rootfs_*/usr/lib/snapd/,
mount options=(rw slave) -> /tmp/snap.rootfs_*/usr/lib/snapd/,
Expand Down
33 changes: 33 additions & 0 deletions cmd/snap-confine/udev-support.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include "../libsnap-confine-private/snap.h"
#include "../libsnap-confine-private/string-utils.h"
#include "../libsnap-confine-private/utils.h"
#include "mount-support-hybris.h"
#include "udev-support.h"

/* Allow access to common devices. */
Expand Down Expand Up @@ -107,6 +108,37 @@ static void sc_udev_allow_nvidia(sc_device_cgroup *cgroup) {
}
}

/** Allow access to hybris devices.
*
* Required by Halium-based GNU/Linux adaptations to make use of certain device nodes.
*
* Note: Binder devices on newer Android kernels reside inside of their own binderfs mountpount.
**/
static void sc_udev_allow_hybris(sc_device_cgroup *cgroup) {
/* Only go on here if this has been identified as a Halium/libhybris system
*
* In case the host happens to have binder available, but isn't identified as
* a system requiring it to drive host-residing Android drivers, then return early,
* otherwise we would open a hole between confined apps and unconfined Anbox or other
* which causes them to communicate over a potentially unmediated IPC interface.
* So only proceed if this has been identified as a Halium distribution.
*/
if (!sc_mount_is_halium_system()) {
return;
}

static const char *binder_paths[] = {"/dev/binderfs/binder", "/dev/binderfs/hwbinder", "/dev/binder",
"/dev/hwbinder"};

// If everything looks alright, allow access to binder IPC via the device cgroup
for (long unsigned int i = 0; i < sizeof(binder_paths) / sizeof(binder_paths[0]); i++) {
struct stat sbuf;
if (stat(binder_paths[i], &sbuf) == 0) {
sc_device_cgroup_allow(cgroup, S_IFCHR, major(sbuf.st_rdev), minor(sbuf.st_rdev));
}
}
}

/**
* Allow access to /dev/uhid.
*
Expand Down Expand Up @@ -182,6 +214,7 @@ static void sc_udev_setup_acls_common(sc_device_cgroup *cgroup) {
sc_udev_allow_common(cgroup);
sc_udev_allow_pty_slaves(cgroup);
sc_udev_allow_nvidia(cgroup);
sc_udev_allow_hybris(cgroup);
sc_udev_allow_uhid(cgroup);
sc_udev_allow_dev_net_tun(cgroup);
}
Expand Down
Loading
Loading