Czy cytowanie nazw plików jest wystarczającym zabezpieczeniem do uruchomienia `xargs sudo rm -rf`?

10

Napisałem skrypt, który usuwa wszystkie oprócz dwóch ostatnich plików w folderze:

#!/bin/bash
ls -1 --quoting-style=shell-always /path/to/some/folder \
    | head -n -2 \
    | xargs printf -- "'/path/to/some/folder/%s'\n" \
    | xargs sudo rm -rf

Ten skrypt będzie wykonywany codziennie jako zadanie crona.

Rozumowanie jest następujące:

  1. Uzyskaj listę wszystkich używanych plików ls -1(aby uzyskać jeden plik w wierszu);

  2. Usuń dwa ostatnie z listy za pomocą head -n -2;

  3. Ponieważ lsdrukuje ścieżki względne, użyj xargs printfrzeczy, aby dodać ścieżkę folderu i uczynić ją ścieżką bezwzględną;

  4. Wyślij je do sudo rm -rfza pomocą xargs.

Każdy ma dostęp do tego folderu, więc każdy może tworzyć i usuwać dowolne pliki w tym folderze.

Problem w tym, że sudo rm -rf jest straszny. xargs sudo rm -rfjest niesamowicie przerażający.

Chcę mieć pewność, że nikt nie może uszkodzić innych folderów / systemów, tworząc sprytne pliki do usunięcia (przypadkowo lub celowo). Nie wiem, coś sprytnego jak:

file with / spaces.txt

co może spowodować bardzo przerażające sudo rm -rf /.

EDYCJA: Mój błąd, nazwy plików nie mogą zawierać /, więc ten konkretny problem nie miałby miejsca, ale pytanie o to, czy istnieją inne zagrożenia, wciąż pozostaje aktualne.

Dlatego używam --quoting-style=shell-always, to powinno zapobiec wszelkim sztuczkom z plikami ze spacjami. Ale teraz zastanawiam się, czy ktoś może być wyjątkowo sprytny ze spacjami i cudzysłowami w nazwie pliku.

Czy mój skrypt jest bezpieczny?


Uwaga: Potrzebuję, sudoponieważ uzyskuję dostęp do folderu zdalnie (przy użyciu zmapowanego dysku sieciowego mount) i nie mogłem go uruchomić bez sudo.

Pedro A.
źródło
3
Czy zastanawiałeś się nad zrobieniem czegoś takiego printf -- '%s\0' /path/to/some/folder/* | head -zn -2 | xargs -0 rm?
steeldriver
Czy plik z charakterem /w imię być tworzone staram się osiągnąć ten tu
George Udosen
3
@George Nie, nazwa pliku nie może zawierać ukośnika.
wjandrea,
Więc kiedy OP mówi sprytną osobę, zastanawiałem się ...
George Udosen
6
Tylko dlatego, że analizujesz lsdane wyjściowe, jest to już źle napisane polecenie, nawet przy cytowaniu. lsmyślę, że używa także ustawień regionalnych do sortowania, więc nie rozumiem, jaki jest cel headusuwania ostatnich 2 (chyba że próbujesz się pozbyć .i ..które iirc nie są dozwolone jako argumenty rm. Po prostu użyj find /path/to/folder -type f delete. I nie, sudojeśli
uciekasz

Odpowiedzi:

10

W systemie Linux dowolny znak jest prawidłową nazwą pliku, z wyjątkiem:

  • \0 (ASCII NUL): używany do zakończenia łańcucha w C
  • / (ukośnik): używany do rozdzielania ścieżek

Więc twoje podejście na pewno nie zadziała w wielu przypadkach, jak możesz sobie wyobrazić, np. Czy obsługuje on newline ( \n) w nazwie pliku? ( Wskazówka: nie ).

Kilka notatek:

  • Nie parsuj ls; użyj dedykowanych narzędzi (istnieje co najmniej jedno dla większości przypadków użycia)
  • Kiedy mamy do czynienia z nazwami plików, starajcie się korzystać z danych wyjściowych oddzielonych od NUL dostarczanych przez prawie wszystkie narzędzia GNU, które działają z takimi danymi
  • Zachowaj ostrożność podczas instalacji, upewnij się, że oba programy mogą zrozumieć separacje NUL
  • Kiedykolwiek inwokujesz xargs, sprawdź, czy możesz uciec find ... -exec; W większości przypadków będzie dobrze ze tylko findsam

Myślę, że na razie cię to uruchomi. steeldriver podał już pomysł rozdzielony przez NUL w komentarzu ( printf -- '%s\0' /path/to/some/folder/* | head -zn -2 | xargs -0 rm), użyj tego jako punktu wyjścia.

heemayl
źródło
Dzięki za odpowiedź :) Myślę, że powinieneś zacytować komentarz steeldrivera zamiast tylko wspomnieć (ponieważ komentarze nie są trwałe). Przyjrzę się findrównież, dzięki za sugestię.
Pedro A
Mam jednak jedno pytanie: nie rozumiem, co rozumiesz przez „twoje podejście zdecydowanie nie zadziała w wielu przypadkach, jak możesz sobie wyobrazić” - „nie działa” jak w „nie bezpieczne” lub „nie niebezpieczne”? Ponieważ „twoje podejście” odnosi się do mnie, a nie do złośliwego użytkownika, a twoje wcześniejsze oświadczenia są na moją korzyść, więc jestem zdezorientowany.
Pedro A,
@PedroA You are you :) Jak powiedziałem, ponieważ wszystkie znaki są poprawne oprócz wymienionych dwóch, powinieneś być w stanie wyobrazić sobie liczne przypadki, w których twoje lsparsowanie nie powiodło się, np. Czy wziąłeś pod uwagę nowy wiersz w nazwie pliku?
heemayl
Och, nowy wiersz w nazwie pliku ... Nie myślałem o tym. Jeśli nie masz nic przeciwko dodaniu tego również do swojej odpowiedzi :) Przepraszam, że pytam, ale co robi head -z? Brzmi to absurdalnie, ale nie mam manani infow moim linuksie kontenera CoreOS ... Nie mogłem też znaleźć w Internecie. Dostajęhead: invalid option 'z'
Pedro A,
@PedroA Newline to tylko jeden przypadek, jest wiele takich, które moglibyście się domyślić . Potrzebujesz GNU head(dostarczany z GNU coreutils). Poniżej znajduje się wersja elektroniczna: manpages.ubuntu.com/manpages/xenial/man1/head.1.html
heemayl
3

xargs obsługuje niektóre cytaty: z pojedynczymi cudzysłowami, podwójnymi cudzysłowami lub odwrotnym ukośnikiem, co pozwala mu akceptować dowolne argumenty¹, ale ze składnią inną niż składnia cytowania powłoki podobnej do Bourne'a.

Implementacja GNU, lstaka jak na Ubuntu, nie ma trybu cytowania zgodnego z xargsformatem wejściowym.

Jest ls --quoting-style=shell-alwayskompatybilny z powłokami ksh93, bash i zsh, które podają składnię, ale tylko wtedy, gdy dane wyjściowe lssą interpretowane przez powłokę w tych samych ustawieniach narodowych, jak lswtedy, gdy ją wypisuje. Ponadto należy unikać niektórych ustawień regionalnych, takich jak BIG5, BIG5-HKSCS, GBK lub GB18030.

Dzięki tym powłokom możesz w rzeczywistości wykonać:

typeset -a files
eval "files=($(ls --quoting-style=shell-always))"
xargs -r0a <(printf '%s\0' "${files[@]:0:3}") ...

Ale to ma niewielką przewagę nad:

files=(*(N))                 # zsh
files=(~(N)*)                # ksh93
shopt -s nullglob; files=(*) # bash

Jedynym przypadkiem, w którym staje się to użyteczne, jest skorzystanie z -topcji lssortowania plików według mtime / atime / ctime lub -S/ -V. Ale nawet wtedy równie dobrze możesz użyć zsh:

files=(*(Nom))

na przykład, aby posortować pliki według mtime (użyj oLdla -Si ndla -V).

Aby usunąć wszystkie oprócz dwóch ostatnio zmodyfikowanych zwykłych plików:

rm -f -- *(D.om[3,-1])

¹ nadal istnieją pewne ograniczenia długości ( execve()w niektórych xargsimplementacjach innych niż GNU i znacznie niższe arbitralne), a niektóre xargsimplementacje inne niż GNU dławią dane wejściowe zawierające sekwencje bajtów nie tworzące prawidłowych znaków.

Stéphane Chazelas
źródło