mpartel / bindfs

Mount a directory elsewhere with changed permissions.
https://bindfs.org/
GNU General Public License v2.0
448 stars 64 forks source link

[Question/Bug maybe?] preserve/mirror file's selinux context #124

Open Mark-Joy opened 1 year ago

Mark-Joy commented 1 year ago

On Android through adb, I have created a file and made an ext4 FS. I created a loop device with losetup and I mounted it on "/mnt/pass_through/0/xMySDCard/com.termux". Then I used bindfs to mirror it to "/data/data/com.termux"

losetup -fs /mnt/pass_through/0/430B-16F1/0-InternalData/InternalData.img
mount /dev/block/loop0 /mnt/pass_through/0/xMySDCard/com.termux
bindfs "/mnt/pass_through/0/xMySDCard/com.termux" "/data/data/com.termux"

Doing ls -laZ "/mnt/pass_through/0/xMySDCard/com.termux" gave:

130|onclite:/ # ls -laZ  "/mnt/pass_through/0/xMySDCard/com.termux"
total 24
drwxrwxr-x 6 u0_a175 u0_a175       u:object_r:app_data_file:s0:c175,c256,c512,c768  4096 2022-12-05 04:10 .
drwxrwxrwx 3 root    root          u:object_r:mnt_pass_through_file:s0                60 2022-12-06 08:11 ..
drwxrws--x 2 u0_a175 u0_a175_cache u:object_r:app_data_file:s0:c175,c256,c512,c768  4096 2022-12-04 05:43 cache
drwxrwx--x 4 u0_a175 u0_a175       u:object_r:app_data_file:s0:c175,c256,c512,c768  4096 2022-12-04 05:43 files
drwxrwx--x 2 u0_a175 u0_a175       u:object_r:app_data_file:s0:c175,c256,c512,c768  4096 2022-12-05 04:44 shared_prefs

Doing ls -laZ "/data/data/com.termux" gave:

onclite:/ # ls -laZ "/data/data/com.termux"
total 44
drwxrwxr-x   6 u0_a175 u0_a175 u:object_r:fuse:s0                         4096 2022-12-05 04:10 .
drwxrwx--x 206 system  system  u:object_r:system_data_file:s0:c512,c768  16384 2022-12-04 09:27 ..
drwxrws--x   2 u0_a175 u0_a175 u:object_r:fuse:s0                         4096 2022-12-04 05:43 cache
drwxrwx--x   4 u0_a175 u0_a175 u:object_r:fuse:s0                         4096 2022-12-04 05:43 files
drwxrwx--x   2 u0_a175 u0_a175 u:object_r:fuse:s0                         4096 2022-12-05 04:44 shared_prefs

Obviously, the selinux contexts are different.

When I tried with standard "mount -o bind"

mount -o bind "/mnt/pass_through/0/xMySDCard/com.termux" "/data/data/com.termux"

I got the correct selinux context


onclite:/ # ls -laZ "/data/data/com.termux"
total 44
drwxrwxr-x   6 u0_a175 u0_a175       u:object_r:app_data_file:s0:c175,c256,c512,c768   4096 2022-12-05 04:10 .
drwxrwx--x 206 system  system        u:object_r:system_data_file:s0:c512,c768         16384 2022-12-04 09:27 ..
drwxrws--x   2 u0_a175 u0_a175_cache u:object_r:app_data_file:s0:c175,c256,c512,c768   4096 2022-12-04 05:43 cache
drwxrwx--x   4 u0_a175 u0_a175       u:object_r:app_data_file:s0:c175,c256,c512,c768   4096 2022-12-04 05:43 files
drwxrwx--x   2 u0_a175 u0_a175       u:object_r:app_data_file:s0:c175,c256,c512,c768   4096 2022-12-05 04:44 shared_prefs```

Because selinux context was not mirrored/preserved, app is broken.

My question is, how to make bindfs behave like "mount -o bind" preserving selinux context?
mpartel commented 1 year ago

Sorry for the late response. Crazy month. bindfs doesn't currently know anything about selinux. Also, I don't currently know much about selinux, and I'm unfortunately way too busy in the foreseeable future to learn :(

The FUSE changelog says "SELinux support" was added in FUSE 2.9.7, and this commit seems to add some relevant options. Hope this helps.

agnostic-apollo commented 1 year ago

I was adding code in termux-app to detect if legacy app data directory path /data/data/com.termux is actually accessible for multi user support and was wondering whether someone would be using bindfs instead of bind mounts, and viola, someone already is! ;)

On Android version <= 10, the /data/user/0 is a symlink to /data/data directory and on Android version >= 11, the /data/data directory is bind mounted at /data/user/0. But bind mount does not exist on secondary users by default, like user 10. So if someone tried to bindfs mount /data/user/10/com.termux on /data/data/com.termux in secondary user, it would fail the check for whether both paths are for the same file, i.e device (st_dev) and inode (st_ino) are same for both paths. Currently, by default, such a case would assume non-accessible, since it would be complex to find if two directories are for the same directory with a fuse mount, since would requiring mount points parsing or creation of random file and checking existence, which may have synchronization delay issues. If using bind mount or symlink, the st_dev and st_ino are same. Also would be same if mounting an image on both paths, mounts are automatically translated with bind and bindfs mount done on single path below. I have only tested on Android 13 avd and not with symlink era.

To answer the OP's query, you can pass -o context=<context> option to bindfs to pass contexts, but its currently broken and for termux usage I would recommend using bind mounts. Persistence of context to actual files would be an issue too with such options and so bind mount should be used instead.

# Install packages
apt update
apt install root-repo
apt install bindfs mount-utils

# Start root shell in global mount namespace
# https://github.com/agnostic-apollo/sudo
sudo --su-run-options=-mm su

# Create 300mb loop image
dd if=/dev/zero of=/data/termux.img bs=1M count=0 seek=300

# Format to ext4
mkfs.ext4 /data/termux.img

# Create mount directory
# Do not try to mount on subdirectory of /data like /data/termux, since
# will result in avc denials to search `/`
# Need to assign `system_data_file` type on parent directory
mkdir -p /data/mount/termux

# Set security context same as /data/data for parent directory of mount directory
# chcon for some reason is only working from root adb shell and not in
# termux even with `setenforce 0`
chcon u:object_r:system_data_file:s0:c512,c768 /data/mount

# Set ownership
chown system:system /data/mount

# Mount termux.img as a loop device
# If using `/system/bin/mount` or its `mount` wrapper provided by termux
# by default with `termux-tools` package, it commadn may fail with
# `losetup: invalid option -- s` (not in root adb shell).
# Install `mount-utils` package to replace wrapper with `coreutils` `mount` binary.
mount -o loop /data/termux.img /data/mount/termux

# Set security context same as /data/data for parent directory of mountpoint/app data directory
# The -h option must be passed, otherwise will result in symlink files to be set to
# `u:object_r:unlabeled:s0` and result in avc denials for lnk_file
# The `c159` is for the user assigned to termux app and must be replaced
# Check `$SE_` env variables or use `/system/bin/ls -ldZ`
# on existing files to get context. The termux provided `ls` will print `?` for security context.
chcon -Rh u:object_r:app_data_file:s0:c159,c256,c512,c768 /data/mount/termux

# Set ownership
# The `10159` is for the user assigned to termux app and must be replaced
# Use `id -u` in termux shell or `$TERMUX_APP__UID` env variable
chown -R 10159:10159 /data/mount/termux

# Set `rwx------` permissions to remove group and others permission
# If different permissions already exist on subdirectories, use
# `find -exec` with `-type` option to fix them. Not required for
# functioning, but better for security while selinux is disabled
chmod 700 /data/mount/termux
# Bind mount loop device mountpoint to app data directory
mount -o bind /data/mount/termux /data/data/com.termux

# Check context
ls -ldZ /data/data/com.termux/files
#drwx------ 5 u0_a159 u0_a159 u:object_r:app_data_file:s0:c159,c256,c512,c768  1024 /data/data/com.termux/files

ls -ldZ /data/data/com.termux/files/usr/bin/sh
#lrwxrwxrwx 1 u0_a159 u0_a159 u:object_r:app_data_file:s0:c159,c256,c512,c768  4 /data/data/com.termux/files/usr/bin/sh -> dash

ls -ldZ /data/data/com.termux/files/usr/bin/dash
#-rwx------ 1 u0_a159 u0_a159 u:object_r:app_data_file:s0:c159,c256,c512,c768  106112 /data/data/com.termux/files/usr/bin/dash

# Check mounts
cat /proc/self/mountinfo | grep termux
#4919 92 7:256 / /data/mount/termux rw,relatime shared:43 - ext4 /dev/block/loop32 rw,seclabel
#4918 92 7:256 / /data/data/com.termux rw,relatime shared:43 - ext4 /dev/block/loop32 rw,seclabel
#4923 99 7:256 / /data_mirror/data_ce/null/0/com.termux rw,relatime shared:43 - ext4 /dev/block/loop32 rw,seclabel
#4920 94 7:256 / /data/user/0/com.termux rw,relatime shared:43 - ext4 /dev/block/loop32 rw,seclabel
# Patch sepolicy temporarily to prevent policy violation and use `deny` again afterwards
# `avc: denied { associate } for scontext=u:object_r:app_data_file:s0:c159 tcontext=u:object_r:app_data_file:s0:c159 tclass=filesystem permissive=0`
# Or use `setenforce 0` temporarily
supolicy --live "allow app_data_file app_data_file filesystem associate"

# Bindfs mount loop device mountpoint to app data directory
# This fails with `fuse: unknown option `c256'` since `bindfs` `1.17.1` uses comma to separate different fuse `-o` options.
bindfs -o nonempty -o 'context=u:object_r:app_data_file:s0:c159,c256,c512,c768' /data/mount/termux /data/data/com.termux

# Mount with the categories removed from `context` since termux app
# process with security context `u:r:untrusted_app_27:s0:c159,c256,c512,c768`
# should still be able to access files without any categories.
# However, even though mount works, the shell doesn't
# avc: denied { read } for name="sh" dev="fuse" ino=16350 scontext=u:r:untrusted_app_27:s0:c159,c256,c512,c768 tcontext=u:object_r:app_data_file:s0 tclass=lnk_file permissive=0 app=com.termux
bindfs -o nonempty -o 'context=u:object_r:app_data_file:s0' /data/mount/termux /data/data/com.termux

# Check context
ls -ldZ /data/data/com.termux/files
#drwxrwx--x 5 u0_a159 u0_a159 u:object_r:app_data_file:s0  1024 /data/data/com.termux/files

ls -ldZ /data/data/com.termux/files/usr/bin/sh
#lrwxrwxrwx 1 u0_a159 u0_a159 u:object_r:app_data_file:s0  4 /data/data/com.termux/files/usr/bin/sh -> dash

ls -ldZ /data/data/com.termux/files/usr/bin/dash
#-rwx------ 1 u0_a159 u0_a159 u:object_r:app_data_file:s0  106112 /data/data/com.termux/files/usr/bin/dash

# Check mounts
cat /proc/self/mountinfo | grep termux
#4919 92 7:256 / /data/mount/termux rw,relatime shared:43 - ext4 /dev/block/loop32 rw,seclabel
#87 92 0:114 / /data/data/com.termux rw,nosuid,nodev,relatime shared:44 - fuse /data/mount/termux rw,context=u:object_r:app_data_file:s0,user_id=0,group_id=0,default_permissions,allow_other
#4920 99 0:114 / /data_mirror/data_ce/null/0/com.termux rw,nosuid,nodev,relatime shared:44 - fuse /data/mount/termux rw,context=u:object_r:app_data_file:s0,user_id=0,group_id=0,default_permissions,allow_other
#4918 94 0:114 / /data/user/0/com.termux rw,nosuid,nodev,relatime shared:44 - fuse /data/mount/termux rw,context=u:object_r:app_data_file:s0,user_id=0,group_id=0,default_permissions,allow_other

I tried patching the sepolicy with supolicy --live "allow untrusted_app_27 app_data_file lnk_file read" and also for open, getattr, setattr, link, unlink and rename, but didn't work. Disabling selinux policy with setenforce 0 does work. I am not sure why this is happening. Maybe selinux is getting confused for a lnk_file without the categories or maybe fuse filesystem itself is broken. Mounting with fscontext or rootcontext option does not work either. Mounting with defcontext fails with fuse: mount failed: Invalid argument.

The c159,c256,c512,c768 categories are part of Multi-Category Security (MCS) and required to Isolate the app data from access by another app and Isolate the app data from one physical user to another. The c512 is for user 0 (primary user), it gets changed to c522 for user 10 (secondary user).

https://source.android.com/docs/security/features/selinux/concepts#security_contexts

https://cs.android.com/android/platform/superproject/+/android-13.0.0_r18:cts/tests/tests/selinux/selinuxTargetSdk28/src/android/security/SELinuxTargetSdkTest.java;l=34;bpv=0

https://github.com/SELinuxProject/selinux-notebook/blob/main/src/mls_mcs.md#multi-level-and-multi-category-security

https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/using_selinux/using-multi-level-security-mls_using-selinux

https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/using_selinux/assembly_using-multi-category-security-mcs-for-data-confidentiality_using-selinux

https://selinuxproject.org/page/NB_SEforAndroid_2#Computing_a_Context

https://github.com/termux/termux-app/blob/v0.118.0/termux-shared/src/main/java/com/termux/shared/file/TermuxFileUtils.java#L200

@mpartel The bindfs -o option will need to be fixed to allow passing commas in context/fscontext/defcontext/rootcontext args, but likely not easily possible so dedicated options would need to be provided instead. Don't think you will need to do anything else, like learning selinux ;) The lnk_file issue is likely not related to bindfs.