TL; DR
find / ! -type l -print0 |
sudo -u "$user" perl -Mfiletest=access -l -0ne 'print if -w'
Вам нужно спросить систему, есть ли у пользователя разрешение на запись. Единственный надежный способ - переключить эффективный uid, эффективный gid и gid дополнения на пользовательский и использовать access(W_OK)системный вызов (даже если это имеет некоторые ограничения для некоторых систем / конфигураций).
И имейте в виду, что отсутствие разрешения на запись в файл не обязательно гарантирует, что вы не сможете изменить содержимое файла по этому пути.
Длинная история
Давайте рассмотрим , что требуется, например , для $ пользователю иметь доступ на запись /foo/file.txt(предполагая , ни один из /fooи /foo/file.txtсимволические ссылки)?
Он нуждается:
- поиск доступа к
/(не нужно read)
- поиск доступа к
/foo(не нужно read)
- доступ для записи в
/foo/file.txt
Вы уже можете видеть, что подходы (например, @ lcd047 или @ apaul ), которые проверяют только разрешение « file.txtне работает», потому что они могут сказать, file.txtчто доступно для записи, даже если у пользователя нет разрешения на поиск /или /foo.
И такой подход, как:
sudo -u "$user" find / -writeble
Также не будет работать, потому что он не будет сообщать о файлах в каталогах, которые пользователь не имеет права на чтение ( findпоскольку он $userне может перечислить их содержимое), даже если он может писать в них.
Если мы забудем о ACL, файловых системах только для чтения, флагах FS (например, неизменяемых), других мерах безопасности (apparmor, SELinux, которые могут даже различать различные типы записи) и сосредоточимся только на традиционных атрибутах прав доступа и владения, чтобы получить дано (поиск или запись) разрешение, это уже довольно сложно и трудно выразить find.
Тебе нужно:
- если файл принадлежит вам, вам нужно это разрешение для владельца (или uid 0)
- если файл не принадлежит вам, но группа принадлежит вам, то вам нужно это разрешение для группы (или uid 0).
- если он не принадлежит вам и не входит ни в одну из ваших групп, то применяются другие разрешения (если ваш uid не равен 0).
В findсинтаксисе, в качестве примера с пользователем uid 1 и gids 1 и 2, это будет:
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
Тот удаляет каталоги, которые пользователь не имеет права на поиск и для других типов файлов (символические ссылки исключены, поскольку они не релевантны), проверяет доступ на запись.
Если вы также хотите рассмотреть возможность записи в каталоги:
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
Или для произвольного $userи его членства в группе, полученного из пользовательской базы данных:
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
(это 3 процесса в общем: id, sedи find)
Лучше всего было бы спускаться по дереву от имени пользователя root и проверять права доступа для каждого файла.
find / ! -type l -exec sudo -u "$user" sh -c '
for file do
[ -w "$file" ] && printf "%s\n" "$file"
done' sh {} +
(это один findпроцесс плюс один sudoи shобрабатывать каждые несколько тысяч файлов, [и printf, как правило , построены в оболочке).
Или с perl:
find / ! -type l -print0 |
sudo -u "$user" perl -Mfiletest=access -l -0ne 'print if -w'
(3 процессы в общей сложности : find, sudoи perl).
Или с zsh:
files=(/**/*(D^@))
USERNAME=$user
for f ($files) {
[ -w $f ] && print -r -- $f
}
(Всего 0 процессов, но весь список файлов сохраняется в памяти)
Эти решения основаны на access(2)системном вызове. То есть вместо того, чтобы воспроизводить алгоритм, который система использует для проверки прав доступа, мы просим систему выполнить эту проверку с помощью того же алгоритма (который учитывает права доступа, ACL, неизменяемые флаги, файловые системы только для чтения ...) ) он будет использовать, если вы попытаетесь открыть файл для записи, так что вы ближе всего найдете надежное решение.
Чтобы протестировать решения, приведенные здесь, с различными комбинациями пользователей, групп и разрешений, вы можете сделать:
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
}
}
}
}
}
}'
Изменение пользователя от 1 до 2 и группы между 1, 2 и 3 и ограничение 9 младшими битами разрешений, так как это уже 9458694 созданных файлов. Это для каталогов, а затем снова для файлов.
Это создает все возможные комбинации u<x>g<y>/<mode1>/u<z>g<w>/<mode2>. Пользователь с uid 1 и gids 1 и 2 будет иметь доступ для записи, u2g1/010/u2g3/777но неu1g2/677/u1g1/777 например.
Теперь все эти решения пытаются определить пути к файлам, которые пользователь может открыть для записи, это отличается от путей, по которым пользователь может изменять содержимое. Чтобы ответить на этот более общий вопрос, необходимо принять во внимание несколько вещей:
- $ user может не иметь доступа для записи,
/a/b/fileно если он владеет file(и имеет доступ к поиску /a/b, и файловая система не доступна только для чтения, и у файла нет неизменяемого флага, и у него есть доступ оболочки к системе), тогда он сможет изменить права доступа fileи предоставить себе доступ.
- То же самое, если он владеет
/a/b но не имеет доступа к поиску.
- $ user может не иметь доступа,
/a/b/fileпотому что у него нет доступа к поиску /aили /a/b, но этот файл может иметь жесткую ссылку, /b/c/fileнапример, в этом случае он может изменить содержимое /a/b/file, открыв его по его /b/c/fileпути.
- То же самое с привязными креплениями . Он может не иметь доступа к поиску
/a, но /a/bможет быть привязан к нему /c, поэтому он может открыться fileдля записи по /c/fileдругому пути.
- Он может не иметь разрешения на запись
/a/b/file, но если у него есть доступ для записи, /a/bон может удалить или переименовать его fileи заменить его своей собственной версией. Он изменил бы содержимое файла на/a/b/file даже если это был бы другой файл.
- То же самое , если у него есть доступ на запись
/a(он мог бы переименовать /a/bв /a/c, создать новый /a/bкаталог и новый fileв нем.
Чтобы найти пути, $userкоторые можно было бы изменить. По адресу 1 или 2 мы больше не можем полагаться на access(2)системный вызов. Мы могли бы изменить наш find -permподход, чтобы разрешить поиск по каталогам или записать доступ к файлам, как только вы станете владельцем:
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
Мы могли бы обратиться к 3 и 4, записав номера устройств и индексов или все файлы, которые $ user имеет разрешение на запись, и сообщать обо всех путях к файлам, которые имеют эти номера dev + inode. На этот раз мы можем использовать более надежный access(2) подходы:
Что-то типа:
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 и 6 на первый взгляд усложняются tбитом разрешений. При применении к каталогам это бит ограниченного удаления, который не позволяет пользователям (кроме владельца каталога) удалять или переименовывать файлы, которые им не принадлежат (даже если они имеют доступ для записи в каталог).
Например, если мы вернемся к нашему предыдущему примеру, если у вас есть доступ к записи /a, то вы должны быть в состоянии переименовать /a/bв /a/c, а затем воссоздать /a/bкаталог и новыми fileтам. Но если tбит включен, /aа вы не являетесь владельцем /a, то вы можете сделать это только при наличии /a/b. Это дает:
- Если у вас есть каталог, как указано в пункте 1, вы можете предоставить себе право на запись, и бит t не применяется (и вы можете удалить его в любом случае), так что вы можете удалить / переименовать / воссоздать любой файл или каталоги в нем, поэтому все пути к файлам под вами ваши переписать с любым контентом.
- Если вы не являетесь владельцем, но имеете право на запись, то:
- Либо
t бит не установлен, и вы в том же случае, что и выше (все пути к файлам ваши).
- или он установлен, и тогда вы не можете изменять файлы, которыми вы не владеете или у которых нет прав на запись, поэтому для нашей цели поиска путей к файлам, которые вы можете изменить, это то же самое, что вообще не иметь разрешения на запись.
Таким образом, мы можем обратиться ко всем 1, 2, 5 и 6 с помощью:
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
Это и решение для 3 и 4 являются независимыми, вы можете объединить их вывод, чтобы получить полный список:
{
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{$_}++'
Как должно быть понятно, если вы уже все прочитали, часть этого, по крайней мере, касается только разрешений и владения, а не других функций, которые могут предоставлять или ограничивать доступ для записи (FS только для чтения, ACL, неизменяемый флаг, другие функции безопасности. ...). И поскольку мы обрабатываем ее в несколько этапов, часть этой информации может быть неправильной, если файлы / каталоги создаются / удаляются / переименовываются или изменяются их права доступа / владельца во время выполнения этого скрипта, как на занятом файловом сервере с миллионами файлов ,
Замечания о переносимости
Весь этот код является стандартным (POSIX, Unix для tбит), кроме:
-print0это расширение GNU, теперь также поддерживаемое несколькими другими реализациями. С findреализациями, которые не поддерживают его, вы можете использовать -exec printf '%s\0' {} +вместо этого и заменить -exec sh -c 'exec find "$@" -print0' sh {} +на -exec sh -c 'exec find "$@" -exec printf "%s\0" {\} +' sh {} +.
perlне указана в POSIX, но широко доступна. Вам нужно perl-5.6.0или выше для -Mfiletest=access.
zshне является командой, указанной в POSIX. Этот zshкод выше должен работать с zsh-3 (1995) и выше.
sudoне является командой, указанной в POSIX. Код должен работать с любой версией, если конфигурация системы позволяет работать perlот имени данного пользователя.