2

I am trying to mount an NFS share on my Android phone.

I have already compiled and installed all the needed kernel modules. The NFS share mounts flawlessly but I can only mount as the system user. This wouldn't be an issue if I could set the correct ownership for the mounted file system. The problem is that the share is always mounted as user=system and group=system, making it inaccessible to regular apps.

I would like to be able to specify the ownership of the mounted filesystem at mount time.

This is how I mount the NFS share

su -c "busybox mount -o nosuid,nolock,nodev,rw,nofail,noatime,intr,tcp,actimeo=1800,context=u:object_r:rootfs:s0,user -t nfs $REMOTE_URI $LOCAL_DIRECTORY"

where REMOTE_URI is the remote location and LOCAL_DIRECTORY is the local directory. The above command is inside a script.

This is the relevant line of the /etc/exports file on the NFS server (a raspberry pi 3)

/media/neo/BLACKBOX XXX.XXX.XXX.XXX/0(rw,insecure,sync,no_root_squash,no_subtree_check,anonuid=1000,anongid=1000)

System Specs:

  • LG V20 H990DS
  • System - V10i-TWN (6th-Nov-17) 7.0 Stock, Rooted & Xposed
  • Kernel - D.O.T.S. v1.4
  • NFS version 4

PS: The UID and GID on the server are 1000 and 1000 respectively. In Android those are reserved for the system user and group. All my other computers have UID and GID 1000 and the same username neo. It would be much easier to just modify how Android mount the share. 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.

1

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:

  1. 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:

  1. 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
    
  2. 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
    
  3. 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 }'
    
  4. 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.

  • 1
    +1 Appreciate the detail – beeshyams Sep 6 '18 at 15:18
0

It seems that every distribution of Busybox that is on Google Play is not compatible with NFSv4 but only NFSv3. So I gave up the idea of using idmapd on Android.

  • Instead, I changed the UID and GID on the NFS server to 4444 so that they don't correspond to the system user anymore.
  • Then I created a new user on Busybox with the same name, UID, and GID as the server (In my Busybox distribution the adduser and addgroup utilities are included).
  • I have purchased Tasker and downloaded this set of tasks from the XDA forum.
  • I have chosen the /mnt/nfs directory to mount the share.

I have no time to investigate which of the previous points is necessary and which is only irrelevant or optional. If you have any idea please write it in a comment.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.