Skip to content

Commit eb44e3e

Browse files
committed
treewide: Ubuntu Touch enablement for graphics & Docker
cmd: Begin implementation of libhybris graphics driver support snap-confine: Additional AppArmor rules for proper libhybris functionality in snap-confine interfaces: Add libhybris AppArmor paths & udev rules to opengl Tested working on the Pixel 3a (Android 9, Qualcomm). snap-confine: Set up hybris environment variables if appropriate Hybris needs the linker and EGL platform paths properly set up to allow running graphical applications using Android drivers. interfaces/opengl: Tag binder & hwbinder appropriately Required to grant access to the respective devices in /dev. cmd/snap-confine: Address review comments - Use PATH_MAX - Commonalize shared functions between NVIDIA & libhybris - Unify and use SC_HYBRIS_PROPERTY_FILE appropriately cmd/snap-confine: Only use one temp buffer for hybris sprintfs interfaces/opengl: Allow AppArmor path for querying Qualcomm driver/gpu model Newer Qualcomm devices (like the Fairphone 4) seem to have their userspace GL libraries query the kernel-mode driver for the respective GPU model. cmd/snap-confine: Unconditionally allow cgroup access to binder devices binder and hwbinder are devices residing in their own filesystem. In order to allow access to them they need to be unconditionally allowed in the cgroup for libhybris drivers to work accordingly. Note that binderfs is checked before binder in order to distinguish between an Android 11+ and lower kernels since newer ones place a compatibility symlink at the old and familiar path. interfaces/builtin: Additionally allow loading Android's i18n APEX As required by some devices, this allows loading i18n APEX into the process in order to allow certain bionic libraries to work properly. Additionally explain the use and necessity for libtls-padding inclusion. interfaces/opengl: Reduce libhybris property context area access to read-only The property area is set up by Halium, confined snaps might merely read it. interfaces/opengl: Ensure hostfs path for libtls-padding.so on hybris The confined snap can only load libs from directories prefixed with /var/lib/snapd/hostfs, so ensure libtls-padding that resides there is mappable into the process. snap-confine: interfaces: Address review comments - Cleanup and remove TLS-padding permit from AppArmor rules - Only allow binder access on Halium/libhybris systems - Die in case the Halium environment cannot be mounted into the target - Restructure binder device mount operation to iterate through a list of allowed device paths release: Add OnTouch as a variant of OnClassic Ubuntu Touch is powered by a similar mechanism to Core for mounting writable paths, though uses a classic filesystem layout and has other expected behavior from Core. To enable Touch having more extensive Snap support just add another bool to determine whether the running system is a Touch image based on a new VARIANT_ID called "touch". This in turn will provide the basis for further Touch enablement like extrausers support. overlord/snapstate: Enable extrausers support on Ubuntu Touch Since Touch also has the group and user databases read-only, with the extrausers databases being the exception, just enforce behavior to match that of Core where it works similarly. cmd: hybris-support: Mount hybris rootfs in all cases Mountpoints are created after the check is fulfillable. cmd: Always allow libhybris support for release.OnTouch The points cross here, needs OnTouch in a way to distinguish needs for libhybris-based devices, and enable them appropriately. cmd: snap-exec: libhybris detection based on SC_EXTRA_LIB setup Set the environment variables required to handle libhybris in a confined environment, with libhybris bionic linker and EGL_PLATFORM implementations available through environment variables. Defaults to wayland for libhybris' libEGL. libgbm support should stay untouched with these variables setting up the linker paths). Tested on Halium-enabled Pixel 3a using Halium (Ubuntu Touch 20.04). Tested Snaps: - Tide IDE (classic Snap running with libhybris on the host) - Gnome Mahjongg (with GDK_DEBUG=gl-disable) - QML Creator (with Wayland QPA) - Yamagi Quake 2 (with SDL & GLES Renderer) interfaces: docker_support: Make it work on Ubuntu Touch For reasons currently unknown Ubuntu Touch port kernels do not work with the set of rules generated by default. Due to not following the requirement of "devmode snaps running other snaps", just check whether it is running OnTouch and avoid filling out the template. interfaces/builtin: Include missing release package in opengl interfaces/builtin: Only set up Android udev tags OnTouch These are giving a lot of permissions to binder and friends, which may not be appropriate for other targets running snapd. So just conditionally enable it when release.OnTouch is set. release: Allow mocking OnTouch environment For future use. release: Fix up MockOnTouch comment daemon, overlord: Enable release.OnTouch in more places Sets up Ubuntu Touch to handle users in a way similar to Core & Core Desktop. cmd/snap-confine: Remove pass-through of host libraries into Snap environment on hybris Also call mount function again, and clean up. cmd/snap-confine: mount-support-hybris: Update Copyright year interfaces: Fix missing change_profiles required for Docker These were missing due to us skipping the AAREExclusionPattern processing. Add regular change_profile directives when running on Ubuntu Touch.
1 parent 6ef8a8e commit eb44e3e

13 files changed

Lines changed: 320 additions & 43 deletions

File tree

cmd/Makefile.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,8 @@ snap_confine_snap_confine_SOURCES = \
269269
snap-confine/cookie-support.h \
270270
snap-confine/mount-support-nvidia.c \
271271
snap-confine/mount-support-nvidia.h \
272+
snap-confine/mount-support-hybris.c \
273+
snap-confine/mount-support-hybris.h \
272274
snap-confine/mount-support.c \
273275
snap-confine/mount-support.h \
274276
snap-confine/ns-support.c \
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* Copyright (C) 2025 Canonical Ltd
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License version 3 as
6+
* published by the Free Software Foundation.
7+
*
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* GNU General Public License for more details.
12+
*
13+
* You should have received a copy of the GNU General Public License
14+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
15+
*
16+
*/
17+
18+
#include "config.h"
19+
#include "mount-support-hybris.h"
20+
21+
#include <errno.h>
22+
#include <fcntl.h>
23+
#include <glob.h>
24+
#include <stdlib.h>
25+
#include <string.h>
26+
#include <sys/mount.h>
27+
#include <sys/stat.h>
28+
#include <sys/types.h>
29+
#include <stdint.h>
30+
#include <unistd.h>
31+
/* POSIX version of basename() and dirname() */
32+
#include <libgen.h>
33+
34+
#include "../libsnap-confine-private/classic.h"
35+
#include "../libsnap-confine-private/cleanup-funcs.h"
36+
#include "../libsnap-confine-private/string-utils.h"
37+
#include "../libsnap-confine-private/utils.h"
38+
#include "mount-support.h"
39+
40+
#define SC_LIBGL_DIR SC_EXTRA_LIB_DIR "/gl"
41+
#define SC_VULKAN_DIR SC_EXTRA_LIB_DIR "/vulkan"
42+
#define SC_GLVND_DIR SC_EXTRA_LIB_DIR "/glvnd"
43+
44+
#define SC_HYBRIS_ROOTFS "/android"
45+
#define SC_HYBRIS_SYSTEM_SYMLINK "/system"
46+
#define SC_HYBRIS_VENDOR_SYMLINK "/vendor"
47+
#define SC_HYBRIS_ODM_SYMLINK "/odm"
48+
#define SC_HYBRIS_APEX_SYMLINK "/apex"
49+
#define SC_HYBRIS_SYSTEM_SYMLINK_TARGET "/android/system"
50+
#define SC_HYBRIS_VENDOR_SYMLINK_TARGET "/android/vendor"
51+
#define SC_HYBRIS_ODM_SYMLINK_TARGET "/android/odm"
52+
#define SC_HYBRIS_APEX_SYMLINK_TARGET "/android/apex"
53+
54+
static void sc_hybris_mount_android_rootfs(const char *rootfs_dir)
55+
{
56+
// Bind mount a tmpfs on $rootfs_dir/$tgt_dir (i.e. /var/lib/snapd/lib/gl)
57+
char path_buf[PATH_MAX] = { 0 };
58+
sc_must_snprintf(path_buf, sizeof(path_buf), "%s%s", rootfs_dir, SC_HYBRIS_ROOTFS);
59+
const char *android_rootfs_dir = path_buf;
60+
61+
int res = mkdir(android_rootfs_dir, 0755);
62+
if (res != 0 && errno != EEXIST) {
63+
die("cannot create bind-mount target %s", android_rootfs_dir);
64+
}
65+
if (res == 0 && (chown(android_rootfs_dir, 0, 0) < 0)) {
66+
// Adjust the ownership only if we created the directory.
67+
die("cannot change ownership of %s", android_rootfs_dir);
68+
}
69+
70+
if (mount(SC_HYBRIS_ROOTFS, android_rootfs_dir, NULL, MS_BIND | MS_REC | MS_RDONLY, NULL)) {
71+
die("Cannot mount Halium environment into target");
72+
}
73+
74+
sc_must_snprintf(path_buf, sizeof(path_buf), "%s%s", rootfs_dir, SC_HYBRIS_SYSTEM_SYMLINK);
75+
const char *android_system_symlink = path_buf;
76+
if (symlink(SC_HYBRIS_SYSTEM_SYMLINK_TARGET, android_system_symlink)) {
77+
die("Cannot set symlink for %s", SC_HYBRIS_SYSTEM_SYMLINK);
78+
}
79+
80+
sc_must_snprintf(path_buf, sizeof(path_buf), "%s%s", rootfs_dir, SC_HYBRIS_VENDOR_SYMLINK);
81+
const char *android_vendor_symlink = path_buf;
82+
if (symlink(SC_HYBRIS_VENDOR_SYMLINK_TARGET, android_vendor_symlink)) {
83+
die("Cannot set symlink for %s", SC_HYBRIS_VENDOR_SYMLINK);
84+
}
85+
86+
sc_must_snprintf(path_buf, sizeof(path_buf), "%s%s", rootfs_dir, SC_HYBRIS_ODM_SYMLINK);
87+
const char *android_odm_symlink = path_buf;
88+
if (symlink(SC_HYBRIS_ODM_SYMLINK_TARGET, android_odm_symlink)) {
89+
die("Cannot set symlink for %s", SC_HYBRIS_ODM_SYMLINK);
90+
}
91+
92+
sc_must_snprintf(path_buf, sizeof(path_buf), "%s%s", rootfs_dir, SC_HYBRIS_APEX_SYMLINK);
93+
const char *android_apex_symlink = path_buf;
94+
if (symlink(SC_HYBRIS_APEX_SYMLINK_TARGET, android_apex_symlink)) {
95+
die("Cannot set symlink for %s", SC_HYBRIS_APEX_SYMLINK);
96+
}
97+
}
98+
99+
void sc_mount_hybris_driver(const char *rootfs_dir, const char *base_snap_name)
100+
{
101+
int res = sc_nonfatal_mkpath(SC_EXTRA_LIB_DIR, 0755, 0, 0);
102+
if (res != 0) {
103+
die("cannot create " SC_EXTRA_LIB_DIR);
104+
}
105+
if (res == 0 && (chown(SC_EXTRA_LIB_DIR, 0, 0) < 0)) {
106+
// Adjust the ownership only if we created the directory.
107+
die("cannot change ownership of " SC_EXTRA_LIB_DIR);
108+
}
109+
110+
sc_hybris_mount_android_rootfs(rootfs_dir);
111+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright (C) 2025 Canonical Ltd
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License version 3 as
6+
* published by the Free Software Foundation.
7+
*
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* GNU General Public License for more details.
12+
*
13+
* You should have received a copy of the GNU General Public License
14+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
15+
*
16+
*/
17+
18+
#ifndef SNAP_CONFINE_MOUNT_SUPPORT_HYBRIS_H
19+
#define SNAP_CONFINE_MOUNT_SUPPORT_HYBRIS_H
20+
21+
/**
22+
* Make the libhybris drivers from the classic distribution available in the snap
23+
* execution environment.
24+
*
25+
* libhybris allows for ABI guarantees as long as their wrappers can be linked or
26+
* dlopen()'ed because it is the library loader, it resolves the symbols and links them.
27+
* /android needs to live inside the Snap environment too for the actual bionic-built
28+
* libraries to be found, loaded and their functions executed.
29+
*
30+
* /android and the respective compatibility symlinks from /system to /android/system
31+
* etc. allow for loading the appropriate userspace components for proper use
32+
* (assuming AppArmor plays along).
33+
**/
34+
void sc_mount_hybris_driver(const char *rootfs_dir, const char *base_snap_name);
35+
36+
#endif

cmd/snap-confine/mount-support-test.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
#include "mount-support.h"
1919
#include "mount-support-nvidia.c"
2020
#include "mount-support-nvidia.h"
21+
#include "mount-support-hybris.c"
22+
#include "mount-support-hybris.h"
2123
#include "mount-support.c"
2224

2325
#include <glib.h>

cmd/snap-confine/mount-support.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
#include "../libsnap-confine-private/tool.h"
5050
#include "../libsnap-confine-private/utils.h"
5151
#include "mount-support-nvidia.h"
52+
#include "mount-support-hybris.h"
5253

5354
#define MAX_BUF 1000
5455
#define SNAP_PRIVATE_TMP_ROOT_DIR "/tmp/snap-private-tmp"
@@ -753,6 +754,7 @@ static void sc_bootstrap_mount_namespace(const struct sc_mount_config *config) {
753754
// pre-pivot filesystem.
754755
if (config->distro == SC_DISTRO_CLASSIC) {
755756
sc_mount_nvidia_driver(scratch_dir, config->base_snap_name);
757+
sc_mount_hybris_driver(scratch_dir, config->base_snap_name);
756758
}
757759
// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
758760
// pivot_root

cmd/snap-confine/snap-confine.apparmor.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,9 @@
265265
mount options=(rw rbind) /mnt/ -> /tmp/snap.rootfs_*/mnt/,
266266
mount options=(rw rslave) -> /tmp/snap.rootfs_*/mnt/,
267267

268+
mount options=(ro rbind) /android/ -> /tmp/snap.rootfs_*/android/,
269+
mount options=(ro rslave) -> /tmp/snap.rootfs_*/android/,
270+
268271
# allow making host snap-exec available inside base snaps
269272
mount options=(rw bind) @LIBEXECDIR@/ -> /tmp/snap.rootfs_*/usr/lib/snapd/,
270273
mount options=(rw slave) -> /tmp/snap.rootfs_*/usr/lib/snapd/,

cmd/snap-confine/udev-support.c

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,35 @@ static void sc_udev_allow_nvidia(sc_device_cgroup *cgroup) {
107107
}
108108
}
109109

110+
/** Allow access to hybris devices.
111+
*
112+
* Required by Halium-based GNU/Linux adaptations to make use of certain device nodes.
113+
*
114+
* Note: Binder devices on newer Android kernels reside inside of their own binderfs mountpount.
115+
**/
116+
static void sc_udev_allow_hybris(sc_device_cgroup *cgroup)
117+
{
118+
/* Only go on here if this has been identified as a Halium/libhybris system
119+
*
120+
* In case the host happens to have binder available, but isn't identified as
121+
* a system requiring it to drive host-residing Android drivers, then return early,
122+
* otherwise we would open a hole between confined apps and unconfined Anbox or other
123+
* which causes them to communicate over a potentially unmediated IPC interface.
124+
*/
125+
struct stat propbuf;
126+
if (stat("/system/build.prop", &propbuf) != 0) {
127+
return;
128+
}
129+
130+
const char *paths[] = {"/dev/binderfs/binder", "/dev/binderfs/hwbinder", "/dev/binder", "/dev/hwbinder"};
131+
for (long unsigned int i = 0; i < sizeof(paths)/sizeof(paths[0]); i++) {
132+
struct stat sbuf;
133+
if (stat(paths[i], &sbuf) == 0) {
134+
sc_device_cgroup_allow(cgroup, S_IFCHR, major(sbuf.st_rdev), minor(sbuf.st_rdev));
135+
}
136+
}
137+
}
138+
110139
/**
111140
* Allow access to /dev/uhid.
112141
*
@@ -182,6 +211,7 @@ static void sc_udev_setup_acls_common(sc_device_cgroup *cgroup) {
182211
sc_udev_allow_common(cgroup);
183212
sc_udev_allow_pty_slaves(cgroup);
184213
sc_udev_allow_nvidia(cgroup);
214+
sc_udev_allow_hybris(cgroup);
185215
sc_udev_allow_uhid(cgroup);
186216
sc_udev_allow_dev_net_tun(cgroup);
187217
}

daemon/api_users.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ func logoutUser(c *Command, r *http.Request, user *auth.UserState) Response {
202202
}
203203

204204
// this might need to become a function, if having user admin becomes a config option
205-
var hasUserAdmin = !release.OnClassic
205+
var hasUserAdmin = !release.OnClassic || release.OnTouch
206206

207207
const noUserAdmin = "system user administration via snapd is not allowed on this system"
208208

interfaces/builtin/docker_support.go

Lines changed: 68 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,24 @@ ptrace (read, trace) peer=unconfined,
723723
###EXCL{<> rwlix,:/snap/snapd/*/usr/lib/snapd/snap-confine,/snap/core/*/usr/lib/snapd/snap-confine}###
724724
`
725725

726+
const dockerSupportConnectedPlugAppArmorTouch = `
727+
# Description: allow docker daemon to change profile
728+
729+
# OnTouch doesn't use AAREExclusionPatterns due to parser/kernel bugs,
730+
# so allow accesses and change_profile the regular way.
731+
/** rwlix,
732+
change_profile unsafe /** -> docker-default,
733+
change_profile unsafe /** -> cri-containerd.apparmor.d,
734+
`
735+
736+
const dockerSupportPrivilegedAppArmorTouch = `
737+
# Description: allow docker daemon to run privileged
738+
739+
# OnTouch doesn't use AAREExclusionPatterns due to parser/kernel bugs,
740+
# so allow change_profile the regular way.
741+
change_profile unsafe /**,
742+
`
743+
726744
const dockerSupportPrivilegedSecComp = `
727745
# Description: allow docker daemon to run privileged containers. This gives
728746
# full access to all resources on the system and thus gives device ownership to
@@ -762,71 +780,82 @@ func (iface *dockerSupportInterface) AppArmorConnectedPlug(spec *apparmor.Specif
762780
// snaps.
763781
spec.SetSuppressPycacheDeny()
764782

765-
defaultSnippet, err := apparmor_sandbox.InsertAAREExclusionPatterns(
766-
dockerSupportConnectedPlugAppArmor,
767-
[]string{
768-
"/snap/snapd/*/usr/lib/snapd/snap-confine",
769-
"/snap/core/*/usr/lib/snapd/snap-confine",
770-
},
771-
&apparmor_sandbox.AAREExclusionPatternsOptions{
772-
Prefix: "change_profile unsafe ",
773-
Suffix: " -> docker-default,",
774-
},
775-
)
776-
if err != nil {
777-
return err
778-
}
779-
780-
defaultSnippet, err = apparmor_sandbox.InsertAAREExclusionPatterns(
781-
defaultSnippet,
782-
[]string{
783-
"/snap/snapd/*/usr/lib/snapd/snap-confine",
784-
"/snap/core/*/usr/lib/snapd/snap-confine",
785-
},
786-
&apparmor_sandbox.AAREExclusionPatternsOptions{
787-
Prefix: "change_profile unsafe ",
788-
Suffix: " -> cri-containerd.apparmor.d,",
789-
},
790-
)
791-
if err != nil {
792-
return err
793-
}
783+
defaultSnippet := dockerSupportConnectedPlugAppArmor
794784

795-
spec.AddSnippet(defaultSnippet)
796-
if privileged {
797-
privilegedSnippet, err := apparmor_sandbox.InsertAAREExclusionPatterns(
798-
dockerSupportPrivilegedAppArmor,
785+
if !release.OnTouch {
786+
defaultSnippet, err := apparmor_sandbox.InsertAAREExclusionPatterns(
787+
dockerSupportConnectedPlugAppArmor,
799788
[]string{
800789
"/snap/snapd/*/usr/lib/snapd/snap-confine",
801790
"/snap/core/*/usr/lib/snapd/snap-confine",
802791
},
803792
&apparmor_sandbox.AAREExclusionPatternsOptions{
804793
Prefix: "change_profile unsafe ",
805-
Suffix: ",",
794+
Suffix: " -> docker-default,",
806795
},
807796
)
808797
if err != nil {
809798
return err
810799
}
811800

812-
privilegedSnippet, err = apparmor_sandbox.InsertAAREExclusionPatterns(
813-
privilegedSnippet,
801+
defaultSnippet, err = apparmor_sandbox.InsertAAREExclusionPatterns(
802+
defaultSnippet,
814803
[]string{
815804
"/snap/snapd/*/usr/lib/snapd/snap-confine",
816805
"/snap/core/*/usr/lib/snapd/snap-confine",
817806
},
818807
&apparmor_sandbox.AAREExclusionPatternsOptions{
819-
Prefix: "",
820-
Suffix: " rwlix,",
808+
Prefix: "change_profile unsafe ",
809+
Suffix: " -> cri-containerd.apparmor.d,",
821810
},
822811
)
823812
if err != nil {
824813
return err
825814
}
815+
} else {
816+
spec.AddSnippet(dockerSupportConnectedPlugAppArmorTouch)
817+
}
826818

819+
spec.AddSnippet(defaultSnippet)
820+
821+
if privileged {
822+
privilegedSnippet := dockerSupportPrivilegedAppArmor
823+
if !release.OnTouch {
824+
privilegedSnippet, err := apparmor_sandbox.InsertAAREExclusionPatterns(
825+
dockerSupportPrivilegedAppArmor,
826+
[]string{
827+
"/snap/snapd/*/usr/lib/snapd/snap-confine",
828+
"/snap/core/*/usr/lib/snapd/snap-confine",
829+
},
830+
&apparmor_sandbox.AAREExclusionPatternsOptions{
831+
Prefix: "change_profile unsafe ",
832+
Suffix: ",",
833+
},
834+
)
835+
if err != nil {
836+
return err
837+
}
838+
839+
privilegedSnippet, err = apparmor_sandbox.InsertAAREExclusionPatterns(
840+
privilegedSnippet,
841+
[]string{
842+
"/snap/snapd/*/usr/lib/snapd/snap-confine",
843+
"/snap/core/*/usr/lib/snapd/snap-confine",
844+
},
845+
&apparmor_sandbox.AAREExclusionPatternsOptions{
846+
Prefix: "",
847+
Suffix: " rwlix,",
848+
},
849+
)
850+
if err != nil {
851+
return err
852+
}
853+
} else {
854+
spec.AddSnippet(dockerSupportPrivilegedAppArmorTouch)
855+
}
827856
spec.AddSnippet(privilegedSnippet)
828857
}
829-
if !release.OnClassic {
858+
if !release.OnClassic || release.OnTouch {
830859
spec.AddSnippet(dockerSupportConnectedPlugAppArmorCore)
831860
}
832861
// if apparmor supports userns mediation then add this too

0 commit comments

Comments
 (0)