I would like to map uid=neo(1000)->root(0) and guid=neo(1000)->sdcard_r(1028) where neo:neo is the user on the server and root:sdcard_r is the user on the phone.
Files ownership root:sdcard_r (0:1028)
on Android won't work for apps. It should be root:everybody (0:9997)
with mode 0770
for directories and 0660
for files.
HOW TO MAKE NFS WORK WITH ANDROID APPS?
Traditional *NIX DAC (UIDs/GIDs) was designed to isolate human users, not programs. On Android devices - meant for one human user usually - apps need to be isolated and protected so that they can't access each other's data. So Android treats every app like a human user - with a unique UID/GID.
External storage (/sdcard
) - whether physically external or internal - is meant to be shared by all apps i.e. multiple UIDs. If it's a traditional *NIX filesystem (like ext4
), every app would create files with its own UID/GID which makes it nearly impossible to share files among all apps with read/write access. To come up with a solution, Android opted emulated filesystem - based on FUSE or sdcardfs
- with fixed ownership and mode. See details in What is the “u#_everybody” UID?
These UIDs/GIDs and modes also control apps' access to files in /sdcard
. So if we want to mount NFS to be accessible by all apps, we need to mitigate these permissions according to Android's storage design.
PLAIN UNIX SECURITY ON NFS:
sec=sys
mode enforces UNIX permissions without any translation. As explained in previous section, files would be created with random ownership by different apps. So those have to be readable/writable by world (o+rw
), otherwise apps won't be able to read/write. But there is no way to enforce file creation mode globally, neither it's a good idea to share files with too open permissions. A possible solution is to use ID mapping and/or anonymous mode.
NFSv4 ID MAPPING:
With NFSv4 it's possible to map different UIDs/GIDs between client and server. If a user with same name but different UIDs/GIDs exists on both sides, files on client look owned by the same user name, not same UID. Linux key-management facility * is used to store mappings of remote user IDs to local user IDs. A good explanation can be found here.
So the idea is to create user:group neo:neo
on NFS server (1000
:1000
) and on Android (0
:9997
). For this, rpc.idmapd
needs to be running on both server and client which queries user database (/etc/{passwd,group}
in simplest case) through NSS. On more recent kernels the NFSv4 client instead uses nfsidmap, and only falls back to rpc.idmapd
if there was a problem running the nfsidmap
. Kernel calls /sbin/request-key
binary in userspace which executes nfsidmap
to query users database. So the kernel would map server's neo
(1000
:1000
) to client's neo
(0
:9997
).
In simple case, if UID/GID range 10000 to 19999 is not assigned to any user/group on NFS server and Nobody-User = root
/Nobody-Group = everybody
is defined in /etc/idmapd.conf
on client, all files (created by Android apps) owned by non-existent users on server would be returned as owned by 0
:9997
on client. But it doesn't resolve the problem of random ownership of files on server.
Running rpc.idmapd
or providing nfsidmap
on Android is a complicated task (static linking and all that). However - making use of keyutils
(request-key
and keyctl
) - we can trick kernel to show fixed ownership 0:9997
for all mappings regardless of what the actual ownership is:
On NFS server:
Run services:
~# mount -t nfsd nfsd /proc/fs/nfsd
~# rpc.mountd -g -N2 -N3 -N4 -N4.1 -N4.2 -p 4000
~# rpc.nfsd -N2 -N3 -N4 -N4.1 -N4.2 -U --p 2049 4
~# mkdir -p /run/rpc_pipefs
~# mount -t rpc_pipefs sunrpc /run/rpc_pipefs
~# rpc.idmapd -S -p /run/rpc_pipefs
~# echo -n N >/sys/module/nfsd/parameters/nfs4_disable_idmapping
~# exportfs -o rw,insecure,hide,no_subtree_check,sec=sys <CLIENT_IP>:/SHARED_DIR
Or configure and run required init
services. Also unblock ports though firewall on client and server.
On Android:
Create /sbin/nfsidmap_pseudo
and /etc/request-key.conf
:
#!/system/bin/sh
uid=0
gid=9997
case $2 in
uid:*)
printf '%s\0' "$uid" | /sbin/keyctl pinstantiate $1 0 ;;
gid:*)
printf '%s\0' "$gid" | /sbin/keyctl pinstantiate $1 0 ;;
*)
# won't do anything to "user" or "group" types
exit 1 ;;
esac
# /etc/request-key.conf
create id_resolver * * /sbin/nfsidmap_pseudo %k %d
Place files, set permissions and enable ID mapping::
~# chmod 0755 /sbin/request-key /sbin/nfsidmap_pseudo /sbin/keyctl
~# chmod 0644 /etc/request-key.conf
~# chown 0.0 /sbin/request-key /sbin/nfsidmap_pseudo /sbin/keyctl /etc/request-key.conf
~# chcon u:object_r:rootfs:s0 /sbin/request-key /sbin/nfsidmap_pseudo /sbin/keyctl
~# chcon u:object_r:system_file:s0 /etc/request-key.conf
~# echo -n N >/sys/module/nfs/parameters/nfs4_disable_idmapping
Since NFS isn't supported officially on Android, SELinux policy doesn't have required rules. You may need to set SELinux permissive or allow kernel to read/write keys, execute files in userspace and make connections:
~# supolicy --live 'allow kernel kernel key { search write view read }'
~# supolicy --live 'allow kernel kernel capability { net_raw net_bind_service sys_admin }'
~# supolicy --live 'allow kernel { rootfs shell_exec system_file } file { execute_no_trans execute open getattr }'
Mount:
~# mkdir -p /sdcard/NFS
~# busybox mount -v -t nfs4 -o vers=4.2,sec=sys,rw,tcp,port=2049,context=u:object_r:sdcardfs:s0 <SERVER_IP>:/SHARED_DIR /mnt/runtime/write/emulated/0/NFS
Unfortunately what we get from all this setup is that now apps apparently see files in /sdcard/NFS
owned by 0:9997
. But with sec=sys
security, actual file access is not governed by NFSv4 UID Mapping **. Permissions are enforced by RPC mechanism which is not yet ready to work with ID mapping. So UID Mapping without Kerberos
security works only if user/group name and number spaces are consistent between the client and server. It means that neo
user on server should have UID/GID: 0
/9997
(which nullifies the whole purpose of ID mapping). On the other hand Kerberos security (sec=krb5
) is too hectic to be tried on Android.
Similarly file locking on NFSv2/3 requires portmapper
(rpcbind
) and rpc.statd
running on server and client both, which is not the case with Android client. So, we need to use nolock
mount option. On NFSv4, however, locking is built-in NFS protocol, NLM isn't needed. So better is to not go for UID Mapping
(on NFSv4, and File Locking
on NFSv2/3). If you need all NFS features (including Kerberos) working on Android device, rather try a tiny Linux distro in chroot
.
* On kernel older than v4.6, /proc/keys
is exposed if kernel is built with KEYS_DEBUG_PROC_KEYS
.
** References: 1, 2, 3, 4, 5, 6
NFS ANONYMOUS ACCESS:
One of NFS security flavors is anonymous mode. Every request from any UID/GID on client is treated as the anonymous UID/GID on server. Initially all files in shared directory must be owned by this UID/GID and all subsequently created files from client side would also have the same ownership:
Export share with sec=none
on server:
~# exportfs -o rw,insecure,no_subtree_check,anonuid=1000,anongid=1000,sec=none,root_squash,all_squash <CLIENT_IP>:/SHARED_DIR
~# chown -R 1000.1000 /SHARED_DIR; chmod 0700 /SHARED_SIR
* For NFSv2/3 also run rpcbind
(on default port 111)
Mount with sec=none
On Android:
~# busybox mount -v -t nfs4 -o vers=4.2,sec=none,rw,tcp,port=2049,context=u:object_r:sdcardfs:s0 <SERVER_IP>:/SHARED_DIR /mnt/runtime/write/emulated/0/NFS
* Use -t
and vers=
according to your kernel build configuration CONFIG_NFS_V[2|3|4|4_1|4_2]
.
* Use -t nfs -o nolock
with vers=3
or vers=2
.
* Make sure you are mounting from root mount namespace.
* Mounting with sec=none
doesn't work with NFSv3.
All apps on Android can now read and write to NFS directory and all files are created with single anonymous UID/GID on server, easy to manage by a user. However apparent ownership (if ID mapping not setup) and permission mode is not according to Android's flavor, so...
TRY IT THE ANDROID WAY...
Let's make use of FUSE or sdcardfs
as Android does:
~# mkdir -p /mnt/NFS /sdcard/NFS
~# busybox mount -v -t nfs4 -o vers=4.2,sec=none,rw,tcp,port=2049,context=u:object_r:sdcardfs:s0 <SERVER_IP>:/SHARED_DIR /mnt/NFS
~# mount -t sdcardfs -o nosuid,nodev,noexec,noatime,mask=7,gid=9997 /mnt/NFS /mnt/runtime/write/emulated/0/NFS
If your device doesn't support sdcardfs
, use bindfs
and replace SELinux context u:object_r:sdcardfs:s0
with u:object_r:fuse:s0
:
~# bindfs -o nosuid,nodev,noexec,noatime,context=u:object_r:fuse:s0 -u 0 -g 9997 -p a-rwx,ug+rw,ugo+X --xattr-none --chown-ignore --chgrp-ignore --chmod-ignore /mnt/NFS /mnt/runtime/write/emulated/0/NFS
It leaves us with no remaining problems. For more details see How to bind mount a folder inside /sdcard with correct permissions?
GIVE CIFS A CHANCE...
NFS being Linux's native filesystem (like ext4
) is meant to enforce *NIX permissions. Non-native filesystems like FAT and CIFS let fixed ownership and permissions be set across whole filesystem. So what you are looking for is relatively easy to achieve with CIFS:
~# mkdir -p /sdcard/CIFS
~# busybox mount -v -t cifs -o vers=3.0,nosuid,nodev,noexec,noatime,rw,hard,context=u:object_r:sdcardfs:s0,nounix,uid=0,gid=9997,file_mode=0660,dir_mode=0770,nouser_xattr,port=445,username=USERNAME,password=PASSWORD //<SERVER_IP>/SHARED_DIR /mnt/runtime/write/emulated/0/CIFS
* Kernel must be built with CONFIG_CIFS
and preferably CONFIG_CIFS_SMB2
, use vers=
accordingly.
* For mount options detail see mount.cifs(8)
OTHER OPTIONS?
Other FUSE-based mountable filesystems are sshfs
and rclone
. Latter offers a large range of protocols and configurations while requiring a very simple setup.