Objectif du cours
Ce chapitre est fondamental en programmation système Linux, parce qu’il explique comment Linux décrit un fichier au-delà de son contenu.
Quand on pense à un fichier, on pense souvent à ses octets :
Code: Select all
Hello world
- son type : fichier normal, dossier, lien symbolique, socket, FIFO, périphérique ;
- ses permissions ;
- son propriétaire ;
- son groupe ;
- sa taille ;
- ses timestamps ;
- son numéro d’inode ;
- le périphérique sur lequel il se trouve ;
- le nombre de hard links ;
- le nombre de blocs réellement alloués ;
- des attributs spéciaux éventuels.
Le cœur du chapitre repose sur cette idée :
Code: Select all
Un fichier Linux n'est pas seulement un nom et un contenu.
C'est une entrée du système de fichiers associée à un inode et à des métadonnées.
Sous Linux, le nom d’un fichier n’est pas le fichier lui-même.
Exemple :
Code: Select all
/home/jean/test.txt
L’inode contient les métadonnées du fichier :
- type du fichier ;
- permissions ;
- UID du propriétaire ;
- GID du groupe ;
- taille ;
- timestamps ;
- nombre de liens physiques ;
- emplacement des blocs de données ;
- flags éventuels.
Code: Select all
nom de fichier -> entrée de répertoire -> inode -> métadonnées + blocs de données
Exemple :
Code: Select all
ln fichier.txt autre_nom.txt
C’est pour ça que `st_nlink` existe dans `struct stat`.
2. Les appels stat(), lstat() et fstat()
Pour récupérer les attributs d’un fichier, on utilise principalement :
Code: Select all
#include <sys/stat.h>
int stat(const char *pathname, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);
int fstat(int fd, struct stat *statbuf);
Code: Select all
struct stat
Différence entre les trois fonctions
- `stat()` travaille à partir d’un chemin.
- `lstat()` travaille aussi à partir d’un chemin, mais traite les liens symboliques différemment.
- `fstat()` travaille à partir d’un descripteur de fichier déjà ouvert.
Code: Select all
struct stat sb;
if (stat("file.txt", &sb) == -1) {
perror("stat");
}
Code: Select all
int fd = open("file.txt", O_RDONLY);
if (fd == -1) {
perror("open");
return 1;
}
struct stat sb;
if (fstat(fd, &sb) == -1) {
perror("fstat");
close(fd);
return 1;
}
close(fd);
C’est l’un des pièges les plus importants du chapitre.
Si le chemin pointe vers un lien symbolique :
Code: Select all
stat()
Alors que :
Code: Select all
lstat()
Exemple :
Code: Select all
ln -s vrai_fichier.txt lien.txt
Code: Select all
stat("lien.txt", &sb);
Code: Select all
vrai_fichier.txt
Code: Select all
lstat("lien.txt", &sb);
Code: Select all
lien.txt
Code: Select all
stat() -> suit le lien symbolique
lstat() -> ne suit pas le lien symbolique
fstat() -> travaille sur un fd déjà ouvert
La structure `struct stat` contient les informations principales sur le fichier.
Une version simplifiée ressemble à ceci :
Code: Select all
struct stat {
dev_t st_dev; /* périphérique contenant le fichier */
ino_t st_ino; /* numéro d'inode */
mode_t st_mode; /* type du fichier + permissions */
nlink_t st_nlink; /* nombre de liens physiques */
uid_t st_uid; /* UID du propriétaire */
gid_t st_gid; /* GID du groupe */
dev_t st_rdev; /* ID périphérique si fichier spécial */
off_t st_size; /* taille en octets */
blksize_t st_blksize; /* taille de bloc optimale pour I/O */
blkcnt_t st_blocks; /* nombre de blocs alloués */
time_t st_atime; /* dernier accès */
time_t st_mtime; /* dernière modification du contenu */
time_t st_ctime; /* dernier changement de métadonnées */
};
Code: Select all
st_mode
st_uid
st_gid
st_size
st_ino
st_nlink
st_atime
st_mtime
st_ctime
st_blocks
st_blksize
Le champ le plus important est :
Code: Select all
st_mode
- le type du fichier ;
- les permissions du fichier.
Il contient aussi l’information qui permet de savoir si le fichier est :
- un fichier normal ;
- un dossier ;
- un lien symbolique ;
- une FIFO ;
- une socket ;
- un périphérique bloc ;
- un périphérique caractère.
Pour tester le type d’un fichier, on utilise des macros.
Code: Select all
S_ISREG(sb.st_mode) /* fichier normal */
S_ISDIR(sb.st_mode) /* dossier */
S_ISLNK(sb.st_mode) /* lien symbolique */
S_ISCHR(sb.st_mode) /* périphérique caractère */
S_ISBLK(sb.st_mode) /* périphérique bloc */
S_ISFIFO(sb.st_mode) /* FIFO / pipe nommé */
S_ISSOCK(sb.st_mode) /* socket */
Code: Select all
struct stat sb;
if (lstat("test", &sb) == -1) {
perror("lstat");
return 1;
}
if (S_ISREG(sb.st_mode)) {
printf("fichier normal\n");
}
else if (S_ISDIR(sb.st_mode)) {
printf("dossier\n");
}
else if (S_ISLNK(sb.st_mode)) {
printf("lien symbolique\n");
}
else if (S_ISFIFO(sb.st_mode)) {
printf("FIFO\n");
}
else if (S_ISSOCK(sb.st_mode)) {
printf("socket\n");
}
else if (S_ISCHR(sb.st_mode)) {
printf("périphérique caractère\n");
}
else if (S_ISBLK(sb.st_mode)) {
printf("périphérique bloc\n");
}
Parce que si `test` est un lien symbolique, `stat()` suivrait la cible. Avec `lstat()`, on peut savoir que `test` est lui-même un lien symbolique.
7. Les permissions classiques UNIX
Les permissions classiques sont :
Code: Select all
r = read
w = write
x = execute
Code: Select all
user
group
other
Code: Select all
-rw-r--r--
Code: Select all
- rw- r-- r--
type user group other
- le propriétaire peut lire et écrire ;
- le groupe peut lire ;
- les autres peuvent lire ;
- personne ne peut exécuter.
Code: Select all
-rwxr-x---
- propriétaire : lecture, écriture, exécution ;
- groupe : lecture, exécution ;
- autres : aucun droit.
En C, on peut tester ou manipuler les permissions avec des constantes.
Code: Select all
S_IRUSR /* user read */
S_IWUSR /* user write */
S_IXUSR /* user execute */
S_IRGRP /* group read */
S_IWGRP /* group write */
S_IXGRP /* group execute */
S_IROTH /* other read */
S_IWOTH /* other write */
S_IXOTH /* other execute */
Code: Select all
if (sb.st_mode & S_IRUSR) {
printf("Le propriétaire peut lire\n");
}
Code: Select all
if (sb.st_mode & S_IXUSR) {
printf("Le propriétaire peut exécuter\n");
}
C’est un point très important.
Sur un fichier :
Code: Select all
r = lire le contenu du fichier
w = modifier le contenu du fichier
x = exécuter le fichier
Code: Select all
r = lister les noms contenus dans le dossier
w = créer, supprimer ou renommer des entrées dans le dossier
x = traverser le dossier
Exemple :
Code: Select all
/home/jean/projet/file.txt
Code: Select all
/
home
jean
projet
Piège classique
Un dossier peut avoir :
Code: Select all
--x
Dans ce cas, on ne peut pas lister le contenu du dossier avec `ls`, mais on peut accéder à un fichier si on connaît déjà son nom exact.
Exemple :
Code: Select all
cat dossier_secret/fichier.txt
10. Algorithme de vérification des permissions
Quand un processus tente d’accéder à un fichier, Linux vérifie les permissions en fonction de :
- l’UID effectif du processus ;
- le GID effectif du processus ;
- les groupes supplémentaires du processus ;
- l’UID propriétaire du fichier ;
- le GID groupe du fichier ;
- les bits de permissions ;
- les capacités éventuelles du processus ;
- les ACL éventuelles si elles existent.
Code: Select all
Si le processus est suffisamment privilégié :
accès autorisé dans beaucoup de cas
Sinon si UID effectif == UID propriétaire du fichier :
utiliser les permissions user
Sinon si GID effectif ou groupes supplémentaires correspondent au groupe du fichier :
utiliser les permissions group
Sinon :
utiliser les permissions other
Code: Select all
Linux ne combine pas les catégories user, group et other.
Code: Select all
-r--rw-rw- user1 users fichier.txt
Permissions user :
Code: Select all
r--
Même si group et other ont `rw-`, ça ne compte pas pour lui.
11. Les UID et GID dans struct stat
Dans `struct stat`, on trouve :
Code: Select all
st_uid
st_gid
`st_gid` est le GID du groupe propriétaire du fichier.
Exemple :
Code: Select all
printf("UID propriétaire : %ld\n", (long) sb.st_uid);
printf("GID groupe : %ld\n", (long) sb.st_gid);
Code: Select all
#include <pwd.h>
struct passwd *pwd;
pwd = getpwuid(sb.st_uid);
if (pwd != NULL) {
printf("propriétaire : %s\n", pwd->pw_name);
}
Code: Select all
#include <grp.h>
struct group *grp;
grp = getgrgid(sb.st_gid);
if (grp != NULL) {
printf("groupe : %s\n", grp->gr_name);
}
Les appels importants :
Code: Select all
#include <unistd.h>
int chown(const char *pathname, uid_t owner, gid_t group);
int lchown(const char *pathname, uid_t owner, gid_t group);
int fchown(int fd, uid_t owner, gid_t group);
- `chown()` change le propriétaire/groupe via un chemin.
- `lchown()` ne suit pas le lien symbolique si le chemin est un lien.
- `fchown()` travaille via un descripteur déjà ouvert.
Code: Select all
if (chown("file.txt", 1000, 1000) == -1) {
perror("chown");
}
Code: Select all
chown("file.txt", -1, 1000);
Code: Select all
chown("file.txt", 1000, -1);
Code: Select all
-1 signifie : ne pas modifier ce champ.
Les appels :
Code: Select all
#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);
`fchmod()` travaille sur un descripteur de fichier.
Exemple :
Code: Select all
chmod("file.txt", 0644);
Code: Select all
rw-r--r--
Code: Select all
chmod("script.sh", 0755);
Code: Select all
rwxr-xr-x
Quand on fait :
Code: Select all
chmod("file.txt", 0644);
On remplace les permissions par la valeur donnée.
Donc si on veut ajouter seulement un droit sans casser le reste, on récupère d’abord les permissions existantes.
Exemple : ajouter seulement le droit d’exécution au propriétaire.
Code: Select all
struct stat sb;
if (stat("file.txt", &sb) == -1) {
perror("stat");
return 1;
}
if (chmod("file.txt", sb.st_mode | S_IXUSR) == -1) {
perror("chmod");
return 1;
}
Code: Select all
sb.st_mode | S_IXUSR
Pour retirer un droit :
Code: Select all
chmod("file.txt", sb.st_mode & ~S_IWOTH);
14. umask()
`umask()` est très important pour comprendre les permissions réellement créées.
Prototype :
Code: Select all
#include <sys/stat.h>
mode_t umask(mode_t mask);
Formule :
Code: Select all
permissions finales = permissions demandées & ~umask
Code: Select all
open("file.txt", O_CREAT | O_WRONLY, 0666);
Code: Select all
0666 = rw-rw-rw-
Code: Select all
022
Résultat :
Code: Select all
0644 = rw-r--r--
Code: Select all
mkdir("dir", 0777);
Code: Select all
0755 = rwxr-xr-x
Code: Select all
umask 022
Code: Select all
fichiers : 0644
dossiers : 0755
Code: Select all
umask 077
Code: Select all
fichiers : 0600
dossiers : 0700
Code: Select all
umask ne donne jamais des droits.
umask retire des droits.
`access()` permet de tester l’accessibilité d’un fichier.
Prototype :
Code: Select all
#include <unistd.h>
int access(const char *pathname, int mode);
Code: Select all
F_OK /* le fichier existe */
R_OK /* lecture possible */
W_OK /* écriture possible */
X_OK /* exécution possible */
Code: Select all
if (access("file.txt", F_OK) == 0) {
printf("le fichier existe\n");
}
else {
printf("le fichier n'existe pas\n");
}
Code: Select all
if (access("file.txt", R_OK) == 0) {
printf("lecture possible\n");
}
else {
printf("lecture impossible\n");
}
`access()` est piégeux dans les programmes privilégiés.
Il teste l’accès avec les identifiants réels du processus, ce qui peut être différent des identifiants effectifs dans un programme setuid.
Autre problème : le TOCTOU.
TOCTOU signifie :
Code: Select all
Time Of Check To Time Of Use
Code: Select all
if (access(path, W_OK) == 0) {
fd = open(path, O_WRONLY);
}
Donc pour une décision de sécurité, il vaut mieux faire l’opération directement et gérer l’erreur.
Mauvaise logique :
Code: Select all
if (access(path, W_OK) == 0) {
open(path, O_WRONLY);
}
Code: Select all
fd = open(path, O_WRONLY);
if (fd == -1) {
perror("open");
}
Dans `struct stat`, on trouve trois timestamps très importants :
Code: Select all
st_atime
st_mtime
st_ctime
- `st_atime` : dernier accès au fichier ;
- `st_mtime` : dernière modification du contenu du fichier ;
- `st_ctime` : dernier changement des métadonnées du fichier.
Mis à jour quand le fichier est lu.
Exemple :
Code: Select all
cat file.txt
st_mtime
Mis à jour quand le contenu du fichier change.
Exemple :
Code: Select all
echo "test" >> file.txt
st_ctime
Mis à jour quand les métadonnées changent.
Exemples :
- changement de permissions avec `chmod` ;
- changement de propriétaire avec `chown` ;
- création ou suppression de hard link ;
- modification du contenu, car la taille ou d’autres métadonnées peuvent changer.
Code: Select all
ctime ne signifie pas creation time.
ctime signifie change time.
Code: Select all
st_ctime = temps du dernier changement de métadonnées
Exemple simple :
Code: Select all
#include <stdio.h>
#include <sys/stat.h>
#include <time.h>
int main(int argc, char *argv[])
{
struct stat sb;
if (argc != 2) {
printf("usage: %s fichier\n", argv[0]);
return 1;
}
if (stat(argv[1], &sb) == -1) {
perror("stat");
return 1;
}
printf("atime : %s", ctime(&sb.st_atime));
printf("mtime : %s", ctime(&sb.st_mtime));
printf("ctime : %s", ctime(&sb.st_ctime));
return 0;
}
Plusieurs appels existent :
Code: Select all
utime()
utimes()
utimensat()
futimens()
Prototype :
Code: Select all
#include <utime.h>
int utime(const char *pathname, const struct utimbuf *buf);
Code: Select all
struct utimbuf {
time_t actime; /* access time */
time_t modtime; /* modification time */
};
Code: Select all
if (utime("file.txt", NULL) == -1) {
perror("utime");
}
Code: Select all
struct utimbuf tb;
tb.actime = 1000000000;
tb.modtime = 1000000000;
if (utime("file.txt", &tb) == -1) {
perror("utime");
}
Code: Select all
utimensat()
futimens()
19. Les bits spéciaux : setuid, setgid et sticky bit
En plus de `rwx`, Linux possède des bits spéciaux :
- set-user-ID ;
- set-group-ID ;
- sticky bit.
Sur un fichier exécutable, le bit setuid fait que le programme s’exécute avec l’UID effectif du propriétaire du fichier.
Exemple visuel :
Code: Select all
-rwsr-xr-x
Exemple classique :
Code: Select all
/usr/bin/passwd
Code: Select all
/etc/shadow
Idée :
Code: Select all
utilisateur normal lance passwd
passwd s'exécute avec UID effectif root
passwd modifie /etc/shadow de manière contrôlée
19.2. set-group-ID
Sur un fichier exécutable, setgid fait que le programme s’exécute avec le GID effectif du groupe propriétaire du fichier.
Exemple :
Code: Select all
-rwxr-sr-x
Sur un dossier, setgid a une utilité très pratique : les fichiers créés dans ce dossier peuvent hériter du groupe du dossier.
Exemple :
Code: Select all
chmod g+s projet/
19.3. sticky bit
Le sticky bit est surtout utile sur les dossiers.
Exemple :
Code: Select all
drwxrwxrwt
Cas typique :
Code: Select all
/tmp
Donc avec le sticky bit :
- tout le monde peut créer un fichier ;
- mais seul le propriétaire du fichier, le propriétaire du dossier ou root peut le supprimer.
Code: Select all
chmod +t /tmp
Les permissions sont souvent données en octal.
Exemples :
Code: Select all
0644 = rw-r--r--
0755 = rwxr-xr-x
0600 = rw-------
0700 = rwx------
0777 = rwxrwxrwx
Code: Select all
0755
7 = user = rwx
5 = group = r-x
5 = other = r-x
Code: Select all
r = 4
w = 2
x = 1
Code: Select all
7 = 4 + 2 + 1 = rwx
6 = 4 + 2 = rw-
5 = 4 + 1 = r-x
4 = 4 = r--
0 = aucun droit
Exemple :
Code: Select all
4755
Exemples :
Code: Select all
4000 = setuid
2000 = setgid
1000 = sticky bit
Code: Select all
4755 = setuid + rwxr-xr-x
2755 = setgid + rwxr-xr-x
1777 = sticky + rwxrwxrwx
Dans `struct stat`, deux champs sont souvent confondus :
Code: Select all
st_size
st_blocks
`st_blocks` donne le nombre de blocs réellement alloués sur le disque.
Exemple :
Code: Select all
printf("taille logique : %ld\n", (long) sb.st_size);
printf("blocs alloués : %ld\n", (long) sb.st_blocks);
Exemple conceptuel :
Code: Select all
début du fichier -> données
grand trou non alloué
fin du fichier -> données
22. st_blksize
`st_blksize` donne une taille de bloc optimale pour les opérations d’entrée/sortie.
Ce n’est pas forcément la taille du fichier.
Ce champ peut aider à choisir une taille de buffer raisonnable.
Exemple :
Code: Select all
char *buffer = malloc(sb.st_blksize);
23. Les flags d’inode : lsattr et chattr
Certains systèmes de fichiers Linux supportent des attributs supplémentaires sur les inodes.
Commandes :
Code: Select all
lsattr fichier
chattr fichier
Code: Select all
lsattr file.txt
Code: Select all
sudo chattr +i file.txt
Code: Select all
sudo chattr -i file.txt
Le flag immutable empêche la modification, suppression ou renommage normal du fichier.
Exemple :
Code: Select all
sudo chattr +i important.conf
Pour le modifier à nouveau :
Code: Select all
sudo chattr -i important.conf
Code: Select all
+i = immutable
25. Le flag append-only
Le flag append-only permet seulement d’ajouter à la fin du fichier.
Commande :
Code: Select all
sudo chattr +a log.txt
C’est utile pour certains logs.
Retirer le flag :
Code: Select all
sudo chattr -a log.txt
Code: Select all
+a = append only
Voici un programme simple qui affiche plusieurs informations importantes.
Code: Select all
#define _DEFAULT_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
int main(int argc, char *argv[])
{
struct stat sb;
struct passwd *pwd;
struct group *grp;
if (argc != 2) {
printf("usage: %s fichier\n", argv[0]);
return 1;
}
if (lstat(argv[1], &sb) == -1) {
perror("lstat");
return 1;
}
printf("inode : %lu\n", (unsigned long) sb.st_ino);
printf("taille : %ld octets\n", (long) sb.st_size);
printf("liens : %lu\n", (unsigned long) sb.st_nlink);
printf("blocs : %ld\n", (long) sb.st_blocks);
printf("block size : %ld\n", (long) sb.st_blksize);
if (S_ISREG(sb.st_mode)) {
printf("type : fichier normal\n");
}
else if (S_ISDIR(sb.st_mode)) {
printf("type : dossier\n");
}
else if (S_ISLNK(sb.st_mode)) {
printf("type : lien symbolique\n");
}
else if (S_ISFIFO(sb.st_mode)) {
printf("type : FIFO\n");
}
else if (S_ISSOCK(sb.st_mode)) {
printf("type : socket\n");
}
else if (S_ISCHR(sb.st_mode)) {
printf("type : périphérique caractère\n");
}
else if (S_ISBLK(sb.st_mode)) {
printf("type : périphérique bloc\n");
}
else {
printf("type : inconnu\n");
}
printf("mode octal : %o\n", sb.st_mode & 07777);
pwd = getpwuid(sb.st_uid);
if (pwd != NULL) {
printf("owner : %s\n", pwd->pw_name);
}
else {
printf("owner uid : %ld\n", (long) sb.st_uid);
}
grp = getgrgid(sb.st_gid);
if (grp != NULL) {
printf("group : %s\n", grp->gr_name);
}
else {
printf("group gid : %ld\n", (long) sb.st_gid);
}
printf("atime : %s", ctime(&sb.st_atime));
printf("mtime : %s", ctime(&sb.st_mtime));
printf("ctime : %s", ctime(&sb.st_ctime));
return 0;
}
Code: Select all
gcc -Wall -Wextra -o fileattr fileattr.c
Code: Select all
./fileattr fichier.txt
./fileattr /tmp
./fileattr lien_symbolique
Ajouter l’exécution au propriétaire :
Code: Select all
#include <stdio.h>
#include <sys/stat.h>
int main(void)
{
struct stat sb;
if (stat("script.sh", &sb) == -1) {
perror("stat");
return 1;
}
if (chmod("script.sh", sb.st_mode | S_IXUSR) == -1) {
perror("chmod");
return 1;
}
return 0;
}
Code: Select all
sb.st_mode | S_IXUSR
Code: Select all
garde les permissions existantes
ajoute le bit execute user
Retirer l’écriture pour les autres :
Code: Select all
#include <stdio.h>
#include <sys/stat.h>
int main(void)
{
struct stat sb;
if (stat("file.txt", &sb) == -1) {
perror("stat");
return 1;
}
if (chmod("file.txt", sb.st_mode & ~S_IWOTH) == -1) {
perror("chmod");
return 1;
}
return 0;
}
Code: Select all
~S_IWOTH
Puis :
Code: Select all
sb.st_mode & ~S_IWOTH
29. Exemple : tester l’existence et l’accès avec access()
Code: Select all
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
if (argc != 2) {
printf("usage: %s fichier\n", argv[0]);
return 1;
}
if (access(argv[1], F_OK) == 0) {
printf("le fichier existe\n");
}
else {
printf("le fichier n'existe pas\n");
return 1;
}
if (access(argv[1], R_OK) == 0) {
printf("lecture possible\n");
}
if (access(argv[1], W_OK) == 0) {
printf("écriture possible\n");
}
if (access(argv[1], X_OK) == 0) {
printf("exécution possible\n");
}
return 0;
}
Code: Select all
access() est pratique pour l'information,
mais il ne faut pas l'utiliser naïvement pour sécuriser une opération sensible.
Code: Select all
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
int main(void)
{
int fd;
umask(0022);
fd = open("test_umask.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);
if (fd == -1) {
perror("open");
return 1;
}
write(fd, "test\n", 5);
close(fd);
return 0;
}
Code: Select all
0666
Code: Select all
umask(0022)
Code: Select all
0644
Récupérer les attributs
Code: Select all
stat()
lstat()
fstat()
Code: Select all
S_ISREG()
S_ISDIR()
S_ISLNK()
S_ISFIFO()
S_ISSOCK()
S_ISCHR()
S_ISBLK()
Code: Select all
chmod()
fchmod()
Code: Select all
chown()
lchown()
fchown()
Code: Select all
access()
Code: Select all
utime()
utimes()
utimensat()
futimens()
Code: Select all
umask()
Code: Select all
ls -l
stat
chmod
chown
chgrp
umask
touch
lsattr
chattr
- `stat()` suit les liens symboliques.
- `lstat()` ne suit pas les liens symboliques.
- `fstat()` travaille sur un descripteur déjà ouvert.
- `st_mode` contient le type du fichier et les permissions.
- `ctime` ne signifie pas creation time.
- `ctime` signifie change time.
- Le droit `x` sur un dossier signifie traverser le dossier.
- Le droit `w` sur un dossier permet de créer, supprimer ou renommer des entrées.
- Linux ne combine pas les permissions user/group/other.
- `chmod(path, 0644)` remplace les permissions existantes.
- Pour ajouter un droit sans casser le reste, il faut lire l’ancien mode puis faire un OR.
- `umask` retire des permissions, il n’en ajoute pas.
- `access()` peut créer un piège TOCTOU si on teste puis on utilise le fichier ensuite.
- Le bit setuid est puissant mais dangereux si le programme est vulnérable.
- Le sticky bit protège les fichiers des autres dans un dossier partagé comme `/tmp`.
- `chattr +i` rend un fichier immutable au niveau du système de fichiers.
Pour un développeur système Linux, ce chapitre est essentiel parce qu’il explique comment raisonner proprement sur les fichiers.
Il faut être capable de répondre à ces questions :
- Est-ce un fichier normal, un dossier ou un lien symbolique ?
- Est-ce que je dois suivre le lien symbolique ou non ?
- Qui possède le fichier ?
- Quel groupe possède le fichier ?
- Quelles sont les permissions exactes ?
- Est-ce que le processus courant a vraiment le droit d’accéder au fichier ?
- Est-ce que les permissions du dossier parent autorisent la traversée ?
- Est-ce que le fichier a des bits spéciaux ?
- Est-ce que `umask` va modifier les permissions créées ?
- Est-ce que je risque un TOCTOU ?
- Est-ce que je dois travailler par chemin ou par descripteur ?
Code: Select all
Quand une opération fichier échoue, il ne faut pas seulement regarder le fichier.
Il faut aussi regarder le chemin, les dossiers parents, l'UID/GID du processus,
les permissions, les liens symboliques, le umask et les flags éventuels.
Code: Select all
stat(path, &sb)
récupère les infos de la cible si path est un lien symbolique
lstat(path, &sb)
récupère les infos du lien lui-même
fstat(fd, &sb)
récupère les infos du fichier déjà ouvert
st_mode
type + permissions
S_ISREG()
fichier normal
S_ISDIR()
dossier
S_ISLNK()
lien symbolique
st_uid
UID propriétaire
st_gid
GID groupe
st_size
taille logique en octets
st_blocks
blocs réellement alloués
st_atime
dernier accès
st_mtime
dernière modification du contenu
st_ctime
dernier changement de métadonnées
chmod()
change les permissions
chown()
change owner/groupe
umask()
retire des permissions à la création
access()
teste l'accessibilité, mais attention aux pièges de sécurité
setuid
prend l'UID effectif du propriétaire du programme
setgid
prend le GID effectif du groupe du programme ou force l'héritage de groupe sur dossier
sticky bit
empêche les utilisateurs de supprimer les fichiers des autres dans un dossier partagé
chattr +i
rend un fichier immutable
Ce chapitre forme une base très importante de la programmation système Linux.
Il relie directement :
- les fichiers ;
- les inodes ;
- les permissions ;
- les UID/GID ;
- les liens symboliques ;
- les timestamps ;
- les droits des dossiers ;
- les bits spéciaux ;
- les flags d’inode ;
- les erreurs de sécurité comme TOCTOU.
Code: Select all
stat()
lstat()
fstat()
st_mode
S_ISREG()
S_ISDIR()
S_ISLNK()
chmod()
chown()
umask()
access()
st_atime
st_mtime
st_ctime
setuid
setgid
sticky bit
Code: Select all
ls -l
stat
chmod
chown
touch
lsattr
Code: Select all
Est-ce que ce processus a le droit d'accéder à ce fichier ?
