¿Cómo montar NFS en Android con los permisos correctos?

Estoy tratando de montar un recurso compartido NFS en mi teléfono Android.

Ya he compilado e instalado todos los módulos del núcleo necesarios. El recurso compartido NFS se monta sin problemas, pero solo puedo montarlo como systemusuario. Esto no sería un problema si pudiera establecer la propiedad correcta para el sistema de archivos montado. El problema es que el recurso compartido siempre se monta como user= systemy group= system, lo que lo hace inaccesible para las aplicaciones normales.

Me gustaría poder especificar la propiedad del sistema de archivos montado en el momento del montaje.

Así es como monto el recurso compartido NFS

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"

donde REMOTE_URI es la ubicación remota y LOCAL_DIRECTORY es el directorio local. El comando anterior está dentro de un script.

Esta es la línea relevante del /etc/exportsarchivo en el servidor NFS (una raspberry pi 3)

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

Especificaciones del sistema:

  • LG V20 H990DS
  • Sistema: V10i-TWN (6 de noviembre de 2017) 7.0 Stock, Rooted y Xposed
  • Núcleo - DOTS v1.4
  • NFS versión 4

PD: El UID y GID en el servidor son 1000 y 1000 respectivamente. En Android, están reservados para el usuario y el grupo del sistema. Todas mis otras computadoras tienen UID y GID 1000 y el mismo nombre de usuario neo. Sería mucho más fácil simplemente modificar cómo Android monta el recurso compartido. Me gustaría mapear uid=neo(1000)->root(0)y guid=neo(1000)->sdcard_r(1028)dónde neo:neoestá el usuario en el servidor y dónde root:sdcard_restá el usuario en el teléfono.

Respuestas (2)

Me gustaría mapear uid=neo(1000)->root(0) y guid=neo(1000)->sdcard_r(1028) donde neo:neo es el usuario en el servidor y root:sdcard_r es el usuario en el teléfono .

La propiedad de archivos root:sdcard_r (0:1028)en Android no funcionará para las aplicaciones. Debe ser root:everybody (0:9997)con modo 0770para directorios y 0660para archivos.

¿CÓMO HACER QUE NFS FUNCIONE CON APLICACIONES DE ANDROID?

El *NIX DAC tradicional (UID/GID) se diseñó para aislar a los usuarios humanos, no a los programas. En los dispositivos Android, destinados generalmente a un usuario humano, las aplicaciones deben aislarse y protegerse para que no puedan acceder a los datos de los demás. Android trata cada aplicación como un usuario humano, con un UID/GID único.

El almacenamiento externo ( /sdcard), ya sea físicamente externo o interno, debe ser compartido por todas las aplicaciones, es decir, varios UID. Si se trata de un sistema de archivos *NIX tradicional (como ext4), cada aplicación crearía archivos con su propio UID/GID, lo que hace que sea casi imposible compartir archivos entre todas las aplicaciones con acceso de lectura/escritura. Para encontrar una solución, Android optó por un sistema de archivos emulado, basado en FUSE o sdcardfs, con propiedad y modo fijos. Ver detalles en ¿Qué es el UID “u#_everybody”?

Estos UID/GID y modos también controlan el acceso de las aplicaciones a los archivos en formato /sdcard. Entonces, si queremos montar NFS para que todas las aplicaciones puedan acceder a ellos, debemos mitigar estos permisos de acuerdo con el diseño de almacenamiento de Android.

SEGURIDAD UNIX SIMPLE EN NFS:

sec=sysEl modo impone los permisos de UNIX sin ninguna traducción. Como se explicó en la sección anterior, diferentes aplicaciones crearían archivos con propiedad aleatoria. Por lo tanto, deben ser legibles/escribibles por todo el mundo ( o+rw), de lo contrario, las aplicaciones no podrán leer/escribir. Pero no hay forma de hacer cumplir el modo de creación de archivos globalmente, ni es una buena idea compartir archivos con permisos demasiado abiertos. Una posible solución es usar el mapeo de ID y/o el modo anónimo.

MAPEO DE IDENTIFICACIÓN DE NFSv4:

Con NFSv4 es posible mapear diferentes UID/GID entre el cliente y el servidor. Si existe un usuario con el mismo nombre pero diferentes UID/GID en ambos lados, los archivos en el cliente parecen propiedad del mismo nombre de usuario, no del mismo UID. La función de administración de claves de Linux * se utiliza para almacenar asignaciones de ID de usuarios remotos a ID de usuarios locales. Una buena explicación se puede encontrar aquí .

Entonces, la idea es crear un usuario: grupo neo:neoen el servidor NFS ( 1000:) 1000y en Android ( 0:) 9997. Para esto, rpc.idmapddebe ejecutarse tanto en el servidor como en el cliente que consulta la base de datos del usuario ( /etc/{passwd,group}en el caso más simple) a través de NSS . En kernels más recientes, el cliente NFSv4 usa nfsidmap y solo recurre a él rpc.idmapdsi hay un problema al ejecutarnfsidmap . Kernel llama a /sbin/request-keybinario en el espacio de usuario que se ejecuta nfsidmappara consultar la base de datos de los usuarios. Entonces, el núcleo asignaría el servidor neo( 1000:) 1000al cliente neo( 0:) 9997.

En un caso simple, si el rango de UID/GID de 10000 a 19999 no está asignado a ningún usuario/grupo en el servidor NFS y / Nobody-User = rootestá Nobody-Group = everybodydefinido en /etc/idmapd.confel cliente, se devolverán todos los archivos (creados por aplicaciones de Android) propiedad de usuarios inexistentes en el servidor. como propiedad de 0: 9997en el cliente. Pero no resuelve el problema de la propiedad aleatoria de archivos en el servidor.

Ejecutar rpc.idmapdo proporcionar nfsidmapen Android es una tarea complicada (enlace estático y todo eso). Sin embargo, haciendo uso de keyutils( request-keyy keyctl), podemos engañar al kernel para que muestre una propiedad fija 0:9997para todas las asignaciones, independientemente de cuál sea la propiedad real:

En el servidor NFS:

  1. Ejecutar servicios:

    ~# 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
    

    O configure y ejecute initlos servicios necesarios. También desbloquee puertos a través del firewall en el cliente y el servidor.

En Android:

  1. Crear /sbin/nfsidmap_pseudoy /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. Coloque archivos, establezca permisos y habilite la asignación de ID:

    ~# 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. Dado que NFS no es compatible oficialmente con Android, la política de SELinux no tiene reglas requeridas. Es posible que deba configurar SELinux como permisivo o permitir que el núcleo lea/escriba claves, ejecute archivos en el espacio de usuario y realice conexiones:

    ~# 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. Montar:

    ~# 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
    

Desafortunadamente, lo que obtenemos de toda esta configuración es que ahora las aplicaciones aparentemente ven archivos /sdcard/NFSpropiedad de 0:9997. Pero con sec=sysla seguridad, el acceso real a los archivos no se rige por la asignación de UID de NFSv4 ** . Los permisos se aplican mediante un mecanismo RPC que aún no está listo para trabajar con la asignación de ID. Por lo tanto, la asignación de UID sin Kerberosseguridad solo funciona si el nombre de usuario/grupo y los espacios numéricos son consistentes entre el cliente y el servidor. Significa que neoel usuario en el servidor debe tener UID/GID: 0/ 9997(lo que anula todo el propósito de la asignación de ID). Por otro lado, la seguridad de Kerberos ( sec=krb5) es demasiado frenética para probarla en Android.

De manera similar, el bloqueo de archivos en NFSv2/3 requiere portmapper( rpcbind) y rpc.statdse ejecuta tanto en el servidor como en el cliente, lo que no es el caso con el cliente de Android. Entonces, necesitamos usar nolockla opción de montaje. En NFSv4, sin embargo, el bloqueo está integrado en el protocolo NFS, no se necesita NLM. Así que mejor es no optar por UID Mapping(en NFSv4 y File Lockingen NFSv2/3). Si necesita que todas las características de NFS (incluido Kerberos) funcionen en un dispositivo Android, pruebe una pequeña distribución de Linux en chroot.

* En kernel anterior a v4.6, /proc/keysestá expuesto si el kernel está construido con KEYS_DEBUG_PROC_KEYS.
** Referencias: 1 , 2 , 3 , 4 , 5 , 6

ACCESO ANÓNIMO NFS:

Uno de los sabores de seguridad de NFS es el modo anónimo . Cada solicitud de cualquier UID/GID en el cliente se trata como el UID/GID anónimo en el servidor. Inicialmente, todos los archivos en el directorio compartido deben ser propiedad de este UID/GID y todos los archivos creados posteriormente desde el lado del cliente también tendrán la misma propiedad:

Exportar compartir con sec=noneel servidor:

~# 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

* Para NFSv2/3 también se ejecuta rpcbind(en el puerto predeterminado 111)

Montar con sec=noneen 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 -ty vers=de acuerdo con la configuración de compilación de su núcleo CONFIG_NFS_V[2|3|4|4_1|4_2].
* Usar -t nfs -o nolockcon vers=3o vers=2.
* Asegúrese de que está montando desde el espacio de nombres de montaje raíz.
* El montaje con sec=none no funciona con NFSv3.

Todas las aplicaciones en Android ahora pueden leer y escribir en el directorio NFS y todos los archivos se crean con un único UID/GID anónimo en el servidor, fácil de administrar por un usuario. Sin embargo, la propiedad aparente (si la asignación de ID no está configurada) y el modo de permiso no están de acuerdo con el sabor de Android, por lo que...

PRUÉBALO AL MODO DE ANDROID...

Hagamos uso de FUSE o sdcardfscomo lo hace Android:

~# 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

Si su dispositivo no es compatible sdcardfs, use bindfsy reemplace el contexto de SELinux u:object_r:sdcardfs:s0con 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

Nos deja sin problemas restantes. Para obtener más detalles, consulte ¿Cómo vincular el montaje de una carpeta dentro de /sdcard con los permisos correctos?

DALE UNA OPORTUNIDAD A CIFS...

NFS, que es el sistema de archivos nativo de Linux (como ext4), está destinado a hacer cumplir los permisos *NIX. Los sistemas de archivos no nativos como FAT y CIFS permiten establecer permisos y propiedad fijos en todo el sistema de archivos. Entonces, lo que está buscando es relativamente fácil de lograr con 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

* El kernel debe construirse con CONFIG_CIFSy, preferiblemente CONFIG_CIFS_SMB2, usarse vers=en consecuencia.
* Para obtener detalles sobre las opciones de montaje, consulte mount.cifs(8)

¿OTRAS OPCIONES?

Otros sistemas de archivos montables basados ​​en FUSE son sshfsy rclone. Este último ofrece una amplia gama de protocolos y configuraciones y requiere una configuración muy sencilla.

+1 Aprecio el detalle
Me encanta la explicación detallada de los fundamentos que afectan esto. ¡Muchas gracias!
@polynomial Me alegro de que haya ayudado.

Parece que todas las distribuciones de Busybox que están en Google Play no son compatibles con NFSv4 sino solo con NFSv3. Así que renuncié a la idea de usar idmapd en Android.

  • En cambio, cambié el UID y el GID en el servidor NFS a 4444 para que systemya no se correspondan con el usuario.
  • Luego creé un nuevo usuario en Busybox con el mismo nombre, UID y GID que el servidor (en mi distribución de Busybox se incluyen las utilidades addusery ).addgroup
  • Compré Tasker y descargué este conjunto de tareas del foro XDA.
  • He elegido el /mnt/nfsdirectorio para montar el recurso compartido.

No tengo tiempo para investigar cuál de los puntos anteriores es necesario y cuál es solo irrelevante u opcional. Si tienes alguna idea, escríbela en un comentario.