Est-ce que le fait de citer les noms de fichiers est suffisamment sécurisé pour exécuter `xargs sudo rm -rf`?


10

J'ai écrit un script qui supprime tous sauf les deux derniers fichiers d'un dossier:

#!/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

Ce script sera exécuté en tant que tâche cron tous les jours.

Le raisonnement est le suivant:

  1. Obtenir une liste de tous les fichiers en utilisant ls -1 (pour que j'obtienne un fichier par ligne);

  2. Supprimez les deux derniers de la liste en utilisant head -n -2;

  3. Puisque ls imprime les chemins relatifs, utilisez l'élément xargs printf pour ajouter le chemin du dossier et en faire un chemin absolu;

  4. Envoyez-les à sudo rm -rf en utilisant xargs.

Tout le monde a accès à ce dossier, donc tout le monde peut créer et supprimer tous les fichiers de ce dossier.

Le problème est: sudo rm -rf est effrayant.xargs sudo rm -rf est incroyablement effrayant.

Je veux être sûr que personne ne peut endommager d'autres dossiers / systèmes en créant des fichiers intelligents à supprimer (accidentellement ou volontairement).Je ne sais pas, quelque chose d'intelligent comme:

file with / spaces.txt

ce qui pourrait entraîner un sudo rm -rf / super effrayant.

EDIT: Mon erreur, les noms de fichiers ne peuvent pas contenir head -n -20, donc ce problème spécifique ne se produirait pas, mais la question de savoir s'il existe ou non d'autres risques demeure.

C'est pourquoi j'utilise head -n -21, cela devrait empêcher toute astuce avec des fichiers avec des espaces.Mais maintenant, je me demande si quelqu'un pourrait être plus intelligent avec des espaces et guillemets dans le nom du fichier, peut-être.

Mon script est-il sécurisé?


Remarque: j'ai besoin de head -n -22 car j'accède au dossier à distance (à partir d'un lecteur réseau mappé en utilisant head -n -23), et je ne pourrais pas le faire fonctionner sans sudo.

10

In Linux, any character is a valid filename constituting character except:

  • \0 (ASCII NUL): as used for string termination in C
  • / (forward slash): as used for path separation

So, your approach will definitely not work in many cases as you can imagine e.g. does it handle a newline (\n) in filename? (Hint: No).

Few notes:

  • Don't parse ls; use dedicated tools (there is at least one for most use cases)
  • When dealing with filenames, try to leverage the NUL separated output provided by almost all GNU tools that work with such data
  • Take care when piping, make sure both programs can understand NUL separations
  • Whenever you're invoking xargs, see if you can get away with find ... -exec; in most cases, you will be fine with just find alone

I think these will get you going for now. steeldriver already provided the NUL separated idea in the comment (printf -- '%s\0' /path/to/some/folder/* | head -zn -2 | xargs -0 rm), use this as a starting point.


3

xargs does support some quoting: with single quotes, double quotes or backslash which allows it to accept arbitrary arguments¹, but with a syntax that is different from the Bourne-like shells' quoting syntax.

The GNU implementation of ls as found on Ubuntu doesn't have any quoting mode that is compatible with the xargs input format.

Its ls --quoting-style=shell-always is compatible with ksh93, bash and zsh shells quoting syntax, but only when the output of ls is interpreted by the shell in the same locale as ls was when it output it. Also, some locales, like those using BIG5, BIG5-HKSCS, GBK or GB18030 should be avoided.

So with those shells, you can actually do:

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

But that has little advantage over:

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

The only case where it becomes useful is when you want to use the -t option of ls to sort the files by mtime/atime/ctime or -S/-V. But even then, you might as well use zsh's:

files=(*(Nom))

for instance to sort the files by mtime (use oL for -S, and n for -V).

To remove all but the two most recently modified regular files:

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

¹ there are still some length limitations (by execve() and in some non-GNU xargs implementations much lower arbitrary ones), and some non-GNU xargs implementations will choke of input that contains sequences of bytes not forming valid characters.