La description dans la open(2)
page de manuel donne quelques indices pour commencer:
O_PATH (since Linux 2.6.39)
Obtain a file descriptor that can be used for two purposes:
to indicate a location in the filesystem tree and to per‐
form operations that act purely at the file descriptor
level. The file itself is not opened, and other file oper‐
ations (e.g., read(2), write(2), fchmod(2), fchown(2),
fgetxattr(2), ioctl(2), mmap(2)) fail with the error EBADF.
Parfois, nous ne voulons pas ouvrir un fichier ou un répertoire. Au lieu de cela, nous voulons juste une référence à cet objet de système de fichiers afin d'effectuer certaines opérations (par exemple, fchdir()
vers un répertoire auquel fait référence un descripteur de fichier que nous avons ouvert à l'aide O_PATH
). Donc, un point trivial: si c'est notre objectif, alors l'ouverture avec O_PATH
devrait être un peu moins chère, car le fichier lui-même n'est pas réellement ouvert.
Et un point moins trivial: avant l'existence de O_PATH
, la manière d'obtenir une telle référence à un objet de système de fichiers était d'ouvrir l'objet avec O_RDONLY
. Mais l'utilisation de O_RDONLY
nécessite que nous ayons l'autorisation de lecture sur l'objet. Cependant, il existe différents cas d'utilisation où nous n'avons pas besoin de lire réellement l'objet: par exemple, exécuter un binaire ou accéder à un répertoire ( fchdir()
) ou atteindre un répertoire pour toucher un objet à l'intérieur du répertoire.
Utilisation avec les appels système "* at ()"
La commune, mais pas la seule, l' utilisation O_PATH
est d'ouvrir un répertoire, afin d'avoir une référence à ce répertoire pour être utilisé avec le « * » à des appels système, tels que openat()
, fstatat()
, fchownat()
et ainsi de suite. Cette famille d'appels système, que nous pouvons penser à peu près comme les successeurs modernes aux anciens appels système avec des noms similaires ( open()
, fstat()
, fchown()
, etc.), servent deux buts, dont le premier vous touchez quand vous demandez " pourquoi est-ce que je veux utiliser un descripteur de fichier au lieu du chemin du répertoire? ". Si nous regardons plus loin dans la open(2)
page de manuel, nous trouvons ce texte (sous une sous-rubrique avec la justification des appels système "* at"):
First, openat() allows an application to avoid race conditions
that could occur when using open() to open files in directories
other than the current working directory. These race conditions
result from the fact that some component of the directory prefix
given to open() could be changed in parallel with the call to
open(). Suppose, for example, that we wish to create the file
path/to/xxx.dep if the file path/to/xxx exists. The problem is
that between the existence check and the file creation step, path
or to (which might be symbolic links) could be modified to point
to a different location. Such races can be avoided by opening a
file descriptor for the target directory, and then specifying that
file descriptor as the dirfd argument of (say) fstatat(2) and ope‐
nat().
Pour rendre cela plus concret ... Supposons que nous ayons un programme qui souhaite effectuer plusieurs opérations dans un répertoire autre que son répertoire de travail actuel, ce qui signifie que nous devons spécifier un préfixe de répertoire dans le cadre des noms de fichiers que nous utilisons. Supposons, par exemple, que le chemin d'accès soit /dir1/dir2/file
et que nous voulons effectuer deux opérations:
- Effectuez une vérification
/dir1/dir2/file
(par exemple, à qui appartient le fichier ou à quelle heure a-t-il été modifié pour la dernière fois).
- Si nous sommes satisfaits du résultat de cette vérification, nous souhaitons peut-être alors effectuer une autre opération du système de fichiers dans le même répertoire, par exemple en créant un fichier appelé
/dir1/dir2/file.new
.
Supposons maintenant que nous ayons tout fait en utilisant des appels système basés sur des chemins traditionnels:
struct stat stabuf;
stat("/dir1/dir2/file", &statbuf);
if ( /* Info returned in statbuf is to our liking */ ) {
fd = open("/dir1/dir2/file.new", O_CREAT | O_RDWR, 0600);
/* And then populate file referred to by fd */
}
Maintenant, supposons en outre que dans le préfixe de répertoire, l' /dir1/dir2
un des composants (disons dir2
) était en fait un lien symbolique (qui fait référence à un répertoire), et qu'entre l'appel stat()
et l'appel àopen()
une personne malveillante a pu changer la cible du lien symbolique dir2
pour pointer vers un répertoire différent. Il s'agit d'une condition de concurrence classique au moment de la vérification du temps d'utilisation. Notre programme a vérifié un fichier dans un répertoire mais a ensuite été amené à créer un fichier dans un répertoire différent - peut-être un répertoire sensible à la sécurité. Le point clé ici est que le chemin d'accès est /dir/dir2
le même, mais ce à quoi il fait référence a complètement changé.
Nous pouvons éviter ce genre de problèmes en utilisant les appels "* at". Tout d'abord, nous obtenons un handle faisant référence au répertoire où nous ferons notre travail:
dirfd = open("/dir/dir2", O_PATH);
Le point critique ici est qu'il dirfd
s'agit d'une référence stable au répertoire auquel faisait référence le chemin /dir1/dir2
au moment de l' open()
appel. Si la cible du lien symbolique dir2
est modifiée par la suite, cela n'affectera pas ce qui dirfd
fait référence. Maintenant, nous pouvons effectuer notre opération check + en utilisant les appels "* at" qui sont équivalents aux appels stat()
et open()
ci-dessus:
fstatat(dirfd, ""file", &statbuf)
struct stat stabuf;
fstatat(dirfd, "file", &statbuf);
if ( /* Info returned in statbuf is to our liking */ ) {
fd = openat(dirfd, "file.new", O_CREAT | O_RDWR, 0600);
/* And then populate file referred to by fd */
}
Au cours de ces étapes, toute manipulation de liens symboliques dans le chemin /dir/dir2
n'aura aucun impact: la vérification ( fstatat()
) et l'opération ( openat()
) sont assurées d'avoir lieu dans le même répertoire.
Il y a un autre but à utiliser les appels "* at ()", qui se rapporte à l'idée de "répertoires de travail actuels par thread" dans les programmes multithread (et encore une fois, nous pourrions ouvrir les répertoires en utilisant O_PATH
), mais je pense que cette utilisation est probablement moins pertinent pour votre question, et je vous laisse lire la open(2)
page de manuel si vous souhaitez en savoir plus.
Utilisation avec des descripteurs de fichiers pour les fichiers normaux
Une utilisation de O_PATH
avec des fichiers normaux consiste à ouvrir un binaire pour lequel nous avons une autorisation d'exécution (mais pas nécessairement une autorisation de lecture, afin que nous ne puissions pas ouvrir le fichier avec O_RDONLY
). Ce descripteur de fichier peut ensuite être transmis à fexecve(3)
pour exécuter le programme. Tout ce que fexecve(fd, argv, envp)
fait son fd
argumentation est essentiellement:
snprintf(buf, "/proc/self/fd/%d", fd);
execve(buf, argv, envp);
(Bien que, à partir de la glibc 2.27, l'implémentation utilise à la place l' execveat(2)
appel système, sur les noyaux qui fournissent cet appel système.)