TL; DR
find / ! -type l -print0 |
sudo -u "$user" perl -Mfiletest=access -l -0ne 'print if -w'
Debe preguntar al sistema si el usuario tiene permiso de escritura. La única forma confiable es cambiar el uid efectivo, el gid efectivo y los gids de suplementación a los del usuario y usar la access(W_OK)
llamada al sistema (incluso eso tiene algunas limitaciones en algunos sistemas / configuraciones).
Y tenga en cuenta que no tener permiso de escritura en un archivo no garantiza necesariamente que no pueda modificar el contenido del archivo en esa ruta.
La historia mas larga
Vamos a considerar lo que se necesita, por ejemplo, para un usuario $ a tener acceso de escritura /foo/file.txt
(suponiendo que ninguno de /foo
y /foo/file.txt
son enlaces simbólicos)?
El necesita:
- buscar acceso a
/
(no es necesario read
)
- buscar acceso a
/foo
(no es necesario read
)
- acceso de escritura a
/foo/file.txt
Ya puede ver que los enfoques (como @ lcd047 o @ apaul's ) que verifican solo el permiso de file.txt
no funcionarán porque podrían decir que file.txt
se puede escribir incluso si el usuario no tiene permiso de búsqueda para /
o /foo
.
Y un enfoque como:
sudo -u "$user" find / -writeble
Tampoco funcionará porque no informará los archivos en directorios en los que el usuario no tiene acceso de lectura (ya find
que $user
no puede enumerar su contenido) incluso si puede escribir en ellos.
Si nos olvidamos de las ACL, los sistemas de archivos de solo lectura, los indicadores FS (como inmutables), otras medidas de seguridad (apparmor, SELinux, que incluso pueden distinguir entre diferentes tipos de escritura) y solo se centran en los permisos tradicionales y los atributos de propiedad, para obtener un dado (buscar o escribir) permiso, eso ya es bastante complicado y difícil de expresar find
.
Necesitas:
- si el archivo es de su propiedad, necesita ese permiso para el propietario (o tiene uid 0)
- si el archivo no es de su propiedad, pero el grupo es uno de los suyos, entonces necesita ese permiso para el grupo (o tiene uid 0).
- si no es de su propiedad y no pertenece a ninguno de sus grupos, se aplican los otros permisos (a menos que su uid sea 0).
En find
sintaxis, aquí como ejemplo con un usuario de uid 1 y gids 1 y 2, eso sería:
find / -type d \
\( \
-user 1 \( -perm -u=x -o -prune \) -o \
\( -group 1 -o -group 2 \) \( -perm -g=x -o -prune \) -o \
-perm -o=x -o -prune \
\) -o -type l -o \
-user 1 \( ! -perm -u=w -o -print \) -o \
\( -group 1 -o -group 2 \) \( ! -perm -g=w -o -print \) -o \
! -perm -o=w -o -print
Que uno ciruelas los directorios que usuario no tiene derecho a buscar y para otros tipos de archivos (enlaces simbólicos excluidos ya que no son relevantes), controles para el acceso de escritura.
Si también desea considerar el acceso de escritura a directorios:
find / -type d \
\( \
-user 1 \( -perm -u=x -o -prune \) -o \
\( -group 1 -o -group 2 \) \( -perm -g=x -o -prune \) -o \
-perm -o=x -o -prune \
\) ! -type d -o -type l -o \
-user 1 \( ! -perm -u=w -o -print \) -o \
\( -group 1 -o -group 2 \) \( ! -perm -g=w -o -print \) -o \
! -perm -o=w -o -print
O para una $user
membresía arbitraria y de grupo recuperada de la base de datos de usuarios:
groups=$(id -G "$user" | sed 's/ / -o -group /g'); IFS=" "
find / -type d \
\( \
-user "$user" \( -perm -u=x -o -prune \) -o \
\( -group $groups \) \( -perm -g=x -o -prune \) -o \
-perm -o=x -o -prune \
\) ! -type d -o -type l -o \
-user "$user" \( ! -perm -u=w -o -print \) -o \
\( -group $groups \) \( ! -perm -g=w -o -print \) -o \
! -perm -o=w -o -print
(que es 3 procesos en total: id
, sed
y find
)
Lo mejor aquí sería descender el árbol como raíz y verificar los permisos como usuario para cada archivo.
find / ! -type l -exec sudo -u "$user" sh -c '
for file do
[ -w "$file" ] && printf "%s\n" "$file"
done' sh {} +
(ese es un find
proceso más uno sudo
y sh
procesa cada pocos miles de archivos, [
y printf
generalmente se construyen en el shell).
O con perl
:
find / ! -type l -print0 |
sudo -u "$user" perl -Mfiletest=access -l -0ne 'print if -w'
(3 procesos en total: find
, sudo
y perl
).
O con zsh
:
files=(/**/*(D^@))
USERNAME=$user
for f ($files) {
[ -w $f ] && print -r -- $f
}
(0 procesos en total, pero almacena la lista completa de archivos en la memoria)
Esas soluciones se basan en la access(2)
llamada al sistema. Es decir, en lugar de reproducir el algoritmo que usa el sistema para verificar el permiso de acceso, le pedimos al sistema que haga esa verificación con el mismo algoritmo (que tiene en cuenta los permisos, las ACL, los indicadores inmutables, los sistemas de archivos de solo lectura ... ) lo usaría si intentas abrir el archivo para escribir, por lo que es lo más cercano a una solución confiable.
Para probar las soluciones dadas aquí, con las diversas combinaciones de usuario, grupo y permisos, puede hacer:
perl -e '
for $u (1,2) {
for $g (1,2,3) {
$d1="u${u}g$g"; mkdir$d1;
for $m (0..511) {
$d2=$d1.sprintf"/%03o",$m; mkdir $d2; chown $u, $g, $d2; chmod $m,$d2;
for $uu (1,2) {
for $gg (1,2,3) {
$d3="$d2/u${uu}g$gg"; mkdir $d3;
for $mm (0..511) {
$f=$d3.sprintf"/%03o",$mm;
open F, ">","$f"; close F;
chown $uu, $gg, $f; chmod $mm, $f
}
}
}
}
}
}'
Variar el usuario entre 1 y 2 y el grupo entre 1, 2 y 3 y limitarnos a los 9 bits inferiores de los permisos, ya que son 9458694 archivos creados. Eso para directorios y luego nuevamente para archivos.
Eso crea todas las combinaciones posibles de u<x>g<y>/<mode1>/u<z>g<w>/<mode2>
. El usuario con uid 1 y gid 1 y 2 tendría acceso de escritura, u2g1/010/u2g3/777
pero no u1g2/677/u1g1/777
por ejemplo.
Ahora, todas esas soluciones intentan identificar las rutas de los archivos que el usuario puede abrir para escribir, eso es diferente de las rutas en las que el usuario puede modificar el contenido. Para responder a esa pregunta más genérica, hay varias cosas a tener en cuenta:
- $ user puede no tener acceso de escritura,
/a/b/file
pero si es el propietario file
(y tiene acceso de búsqueda /a/b
, y el sistema de archivos no es de solo lectura, y el archivo no tiene el indicador inmutable, y tiene acceso de shell al sistema), entonces él podría cambiar los permisos del file
y otorgarse acceso.
- Lo mismo si posee
/a/b
pero no tiene acceso de búsqueda.
- $ usuario no puede tener acceso a
/a/b/file
porque no tiene acceso a la búsqueda /a
o /a/b
, pero ese archivo puede tener un enlace duro en /b/c/file
, por ejemplo, en cuyo caso puede ser capaz de modificar el contenido de /a/b/file
abriéndolo a través de su /b/c/file
trayectoria.
- Lo mismo con los soportes de unión . Es posible que no tenga acceso de búsqueda
/a
, pero /a/b
puede estar montado en un enlace/c
, por lo que podría abrir file
para escribir a través de su /c/file
otra ruta.
- Es posible que no tenga permisos de escritura
/a/b/file
, pero si tiene acceso de escritura /a/b
puede eliminar o cambiar el nombre file
allí y reemplazarlo con su propia versión. Cambiaría el contenido del archivo /a/b/file
incluso si fuera un archivo diferente.
- Lo mismo si él tiene acceso de escritura
/a
(que podría cambiar el nombre /a/b
a /a/c
, crear un nuevo /a/b
directorio y un nuevo file
en ella.
Para encontrar las rutas que $user
podrían modificarse. Para abordar 1 o 2, ya no podemos confiar en la access(2)
llamada al sistema. Podríamos ajustar nuestro find -perm
enfoque para asumir el acceso de búsqueda a los directorios, o escribir el acceso a los archivos tan pronto como usted sea el propietario:
groups=$(id -G "$user" | sed 's/ / -o -group /g'); IFS=" "
find / -type d \
\( \
-user "$user" -o \
\( -group $groups \) \( -perm -g=x -o -prune \) -o \
-perm -o=x -o -prune \
\) ! -type d -o -type l -o \
-user "$user" -print -o \
\( -group $groups \) \( ! -perm -g=w -o -print \) -o \
! -perm -o=w -o -print
Podríamos abordar 3 y 4, registrando el dispositivo y los números de inodo o todos los archivos que $ user tiene permiso para escribir e informar todas las rutas de archivos que tienen esos números dev + inode. Esta vez, podemos usar los access(2)
enfoques basados en más confiables :
Algo como:
find / ! -type l -print0 |
sudo -u "$user" perl -Mfiletest=access -0lne 'print 0+-w,$_' |
perl -l -0ne '
($w,$p) = /(.)(.*)/;
($dev,$ino) = stat$p or next;
$writable{"$dev,$ino"} = 1 if $w;
push @{$p{"$dev,$ino"}}, $p;
END {
for $i (keys %writable) {
for $p (@{$p{$i}}) {
print $p;
}
}
}'
5 y 6 son a primera vista complicados por el t
poco de los permisos. Cuando se aplica en los directorios, ese es el bit de eliminación restringido que impide que los usuarios (que no sean el propietario del directorio) eliminen o renombren los archivos que no poseen (aunque tengan acceso de escritura al directorio).
Por ejemplo, si volvemos a nuestro ejemplo anterior, si tiene acceso de escritura /a
, entonces usted debería ser capaz de cambiar el nombre /a/b
de /a/c
, y volver a crear un /a/b
directorio y un nuevo file
allí. Pero si el t
bit está activado /a
y usted no posee /a
, entonces solo puede hacerlo si posee /a/b
. Eso da:
- Si posee un directorio, según 1, puede otorgarse acceso de escritura, y el bit t no se aplica (y podría eliminarlo de todos modos), por lo que puede eliminar / renombrar / recrear cualquier archivo o directorio allí, por lo que todas las rutas de archivos que se encuentran debajo son suyas para reescribir con cualquier contenido.
- Si no lo posee pero tiene acceso de escritura, entonces:
- O bien el
t
bit no está configurado, y usted está en el mismo caso que el anterior (todas las rutas de archivos son suyas).
- o está configurado y luego no puede modificar los archivos que no posee o no tiene acceso de escritura, por lo que para nuestro propósito de encontrar las rutas de archivos que puede modificar, es lo mismo que no tener permiso de escritura.
Entonces podemos abordar todos los 1, 2, 5 y 6 con:
find / -type d \
\( \
-user "$user" -prune -exec find {} + -o \
\( -group $groups \) \( -perm -g=x -o -prune \) -o \
-perm -o=x -o -prune \
\) ! -type d -o -type l -o \
-user "$user" \( -type d -o -print \) -o \
\( -group $groups \) \( ! -perm -g=w -o \
-type d ! -perm -1000 -exec find {} + -o -print \) -o \
! -perm -o=w -o \
-type d ! -perm -1000 -exec find {} + -o \
-print
Eso y la solución para 3 y 4 son independientes, puede fusionar su salida para obtener una lista completa:
{
find / ! -type l -print0 |
sudo -u "$user" perl -Mfiletest=access -0lne 'print 0+-w,$_' |
perl -0lne '
($w,$p) = /(.)(.*)/;
($dev,$ino) = stat$p or next;
$writable{"$dev,$ino"} = 1 if $w;
push @{$p{"$dev,$ino"}}, $p;
END {
for $i (keys %writable) {
for $p (@{$p{$i}}) {
print $p;
}
}
}'
find / -type d \
\( \
-user "$user" -prune -exec sh -c 'exec find "$@" -print0' sh {} + -o \
\( -group $groups \) \( -perm -g=x -o -prune \) -o \
-perm -o=x -o -prune \
\) ! -type d -o -type l -o \
-user "$user" \( -type d -o -print0 \) -o \
\( -group $groups \) \( ! -perm -g=w -o \
-type d ! -perm -1000 -exec sh -c 'exec find "$@" -print0' sh {} + -o -print0 \) -o \
! -perm -o=w -o \
-type d ! -perm -1000 -exec sh -c 'exec find "$@" -print0' sh {} + -o \
-print0
} | perl -l -0ne 'print unless $seen{$_}++'
Como debe quedar claro si ha leído todo hasta ahora, parte de él al menos solo trata con los permisos y la propiedad, no con las otras características que pueden otorgar o restringir el acceso de escritura (FS de solo lectura, ACL, indicador inmutable, otras características de seguridad ...) Y a medida que lo procesamos en varias etapas, parte de esa información puede ser incorrecta si los archivos / directorios se crean / eliminan / cambian de nombre o se modifican sus permisos / propiedad mientras se ejecuta ese script, como en un servidor de archivos ocupado con millones de archivos .
Notas de portabilidad
Todo ese código es estándar (POSIX, Unix para t
bit) excepto:
-print0
es una extensión de GNU que ahora también es compatible con algunas otras implementaciones. Con find
implementaciones que carecen de soporte para ello, puede usar -exec printf '%s\0' {} +
en su lugar y reemplazar -exec sh -c 'exec find "$@" -print0' sh {} +
con -exec sh -c 'exec find "$@" -exec printf "%s\0" {\} +' sh {} +
.
perl
no es un comando especificado por POSIX pero está ampliamente disponible. Necesitas perl-5.6.0
o más para -Mfiletest=access
.
zsh
no es un comando especificado por POSIX. Ese zsh
código anterior debería funcionar con zsh-3 (1995) y superior.
sudo
no es un comando especificado por POSIX. El código debería funcionar con cualquier versión siempre que la configuración del sistema permita la ejecución perl
del usuario dado.