File Attributes sous Linux : stat(), permissions, timestamps, ownership, umask, chmod et flags d’inode

Ce forum est dédié à apprendre le développement de programmes user mode sur Linux

Moderator: Rick

Post Reply
Hydraxx
Site Admin
Posts: 46
Joined: Mon Jan 12, 2026 4:04 pm
Location: France
Contact:

File Attributes sous Linux : stat(), permissions, timestamps, ownership, umask, chmod et flags d’inode

Post by Hydraxx »

File Attributes sous Linux : stat(), permissions, timestamps, ownership, umask, chmod et flags d’inode

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
Mais pour le noyau, un fichier possède aussi des métadonnées :
  • 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.
Toutes ces informations sont appelées des file attributes.

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.
1. La notion d’inode

Sous Linux, le nom d’un fichier n’est pas le fichier lui-même.

Exemple :

Code: Select all

/home/jean/test.txt
Le chemin permet de trouver une entrée dans un dossier. Cette entrée pointe vers un inode.

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.
Donc mentalement :

Code: Select all

nom de fichier -> entrée de répertoire -> inode -> métadonnées + blocs de données
Un même inode peut avoir plusieurs noms si on crée des hard links.

Exemple :

Code: Select all

ln fichier.txt autre_nom.txt
Ici, les deux noms peuvent pointer vers le même inode.

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);
Ces trois fonctions remplissent une structure :

Code: Select all

struct stat
avec les informations du fichier.

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.
Exemple avec `stat()` :

Code: Select all

struct stat sb;

if (stat("file.txt", &sb) == -1) {
    perror("stat");
}
Exemple avec `fstat()` :

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);
3. Différence importante entre stat() et lstat()

C’est l’un des pièges les plus importants du chapitre.

Si le chemin pointe vers un lien symbolique :

Code: Select all

stat()
suit le lien symbolique et donne les informations de la cible.

Alors que :

Code: Select all

lstat()
ne suit pas le lien symbolique et donne les informations du lien lui-même.

Exemple :

Code: Select all

ln -s vrai_fichier.txt lien.txt
Si on fait :

Code: Select all

stat("lien.txt", &sb);
on récupère les attributs de :

Code: Select all

vrai_fichier.txt
Mais si on fait :

Code: Select all

lstat("lien.txt", &sb);
on récupère les attributs du lien symbolique :

Code: Select all

lien.txt
À retenir :

Code: Select all

stat()  -> suit le lien symbolique
lstat() -> ne suit pas le lien symbolique
fstat() -> travaille sur un fd déjà ouvert
4. La structure struct stat

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 */
};
Les champs les plus importants à maîtriser au début :

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
5. st_mode : type du fichier + permissions

Le champ le plus important est :

Code: Select all

st_mode
Il contient deux informations différentes :
  • le type du fichier ;
  • les permissions du fichier.
Donc `st_mode` ne contient pas seulement `rwx`.

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.
6. Tester le type d’un fichier

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 */
Exemple :

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");
}
Pourquoi utiliser lstat() ici ?

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
Elles sont définies pour trois catégories :

Code: Select all

user
group
other
Exemple :

Code: Select all

-rw-r--r--
Découpage :

Code: Select all

-     rw-     r--     r--
type  user    group   other
Donc :
  • le propriétaire peut lire et écrire ;
  • le groupe peut lire ;
  • les autres peuvent lire ;
  • personne ne peut exécuter.
Autre exemple :

Code: Select all

-rwxr-x---
Signification :
  • propriétaire : lecture, écriture, exécution ;
  • groupe : lecture, exécution ;
  • autres : aucun droit.
8. Les constantes de permissions

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 */
Exemple pour tester si le propriétaire a le droit de lecture :

Code: Select all

if (sb.st_mode & S_IRUSR) {
    printf("Le propriétaire peut lire\n");
}
Exemple pour tester si le propriétaire peut exécuter :

Code: Select all

if (sb.st_mode & S_IXUSR) {
    printf("Le propriétaire peut exécuter\n");
}
9. Différence entre permissions sur fichier et permissions sur dossier

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
Sur un dossier :

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
Le droit `x` sur un dossier est donc un droit de passage.

Exemple :

Code: Select all

/home/jean/projet/file.txt
Pour accéder à `file.txt`, il faut pouvoir traverser :

Code: Select all

/
home
jean
projet
Donc il faut le droit `x` sur chaque dossier du chemin.

Piège classique

Un dossier peut avoir :

Code: Select all

--x
Sans droit de lecture.

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
peut fonctionner si on a `x` sur le dossier et les droits nécessaires sur le fichier.

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.
Simplifié :

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
Point très important :

Code: Select all

Linux ne combine pas les catégories user, group et other.
Exemple piégeux :

Code: Select all

-r--rw-rw-  user1 users fichier.txt
Si `user1` tente d’écrire dans le fichier, Linux regarde les permissions `user`, parce que `user1` est propriétaire.

Permissions user :

Code: Select all

r--
Donc pas d’écriture.

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_uid` est l’UID du propriétaire du fichier.

`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);
Pour convertir un UID en nom utilisateur, on peut utiliser :

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);
}
Pour convertir un GID en nom de groupe :

Code: Select all

#include <grp.h>

struct group *grp;

grp = getgrgid(sb.st_gid);

if (grp != NULL) {
    printf("groupe : %s\n", grp->gr_name);
}
12. Changer le propriétaire : chown(), lchown(), fchown()

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);
Différences :
  • `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.
Exemple :

Code: Select all

if (chown("file.txt", 1000, 1000) == -1) {
    perror("chown");
}
Pour ne changer que le groupe :

Code: Select all

chown("file.txt", -1, 1000);
Pour ne changer que le propriétaire :

Code: Select all

chown("file.txt", 1000, -1);
À retenir :

Code: Select all

-1 signifie : ne pas modifier ce champ.
13. Modifier les permissions : chmod() et fchmod()

Les appels :

Code: Select all

#include <sys/stat.h>

int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);
`chmod()` travaille sur un chemin.

`fchmod()` travaille sur un descripteur de fichier.

Exemple :

Code: Select all

chmod("file.txt", 0644);
Cela donne :

Code: Select all

rw-r--r--
Exemple :

Code: Select all

chmod("script.sh", 0755);
Cela donne :

Code: Select all

rwxr-xr-x
Attention importante

Quand on fait :

Code: Select all

chmod("file.txt", 0644);
on ne rajoute pas simplement des permissions.

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;
}
Ici :

Code: Select all

sb.st_mode | S_IXUSR
ajoute le bit d’exécution pour le propriétaire sans retirer les autres droits.

Pour retirer un droit :

Code: Select all

chmod("file.txt", sb.st_mode & ~S_IWOTH);
Ici on retire l’écriture pour `other`.

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);
Le `umask` indique les permissions à retirer lors de la création d’un fichier ou dossier.

Formule :

Code: Select all

permissions finales = permissions demandées & ~umask
Exemple :

Code: Select all

open("file.txt", O_CREAT | O_WRONLY, 0666);
On demande :

Code: Select all

0666 = rw-rw-rw-
Si le umask vaut :

Code: Select all

022
Linux retire l’écriture au groupe et aux autres.

Résultat :

Code: Select all

0644 = rw-r--r--
Autre exemple :

Code: Select all

mkdir("dir", 0777);
Avec umask `022`, résultat :

Code: Select all

0755 = rwxr-xr-x
Valeurs classiques

Code: Select all

umask 022
Donne généralement :

Code: Select all

fichiers : 0644
dossiers : 0755

Code: Select all

umask 077
Donne généralement :

Code: Select all

fichiers : 0600
dossiers : 0700
À retenir :

Code: Select all

umask ne donne jamais des droits.
umask retire des droits.
15. access()

`access()` permet de tester l’accessibilité d’un fichier.

Prototype :

Code: Select all

#include <unistd.h>

int access(const char *pathname, int mode);
Modes possibles :

Code: Select all

F_OK  /* le fichier existe */
R_OK  /* lecture possible */
W_OK  /* écriture possible */
X_OK  /* exécution possible */
Exemple :

Code: Select all

if (access("file.txt", F_OK) == 0) {
    printf("le fichier existe\n");
}
else {
    printf("le fichier n'existe pas\n");
}
Exemple :

Code: Select all

if (access("file.txt", R_OK) == 0) {
    printf("lecture possible\n");
}
else {
    printf("lecture impossible\n");
}
Attention sécurité

`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
Exemple dangereux :

Code: Select all

if (access(path, W_OK) == 0) {
    fd = open(path, O_WRONLY);
}
Entre le test `access()` et l’appel `open()`, un attaquant peut remplacer le fichier, par exemple avec un lien symbolique.

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);
}
Meilleure logique :

Code: Select all

fd = open(path, O_WRONLY);

if (fd == -1) {
    perror("open");
}
16. Les timestamps : atime, mtime, ctime

Dans `struct stat`, on trouve trois timestamps très importants :

Code: Select all

st_atime
st_mtime
st_ctime
Signification :
  • `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.
st_atime

Mis à jour quand le fichier est lu.

Exemple :

Code: Select all

cat file.txt
peut mettre à jour `atime`.

st_mtime

Mis à jour quand le contenu du fichier change.

Exemple :

Code: Select all

echo "test" >> file.txt
change `mtime`.

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.
Piège majeur :

Code: Select all

ctime ne signifie pas creation time.
ctime signifie change time.
Donc :

Code: Select all

st_ctime = temps du dernier changement de métadonnées
17. Afficher les timestamps

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;
}
18. Modifier les timestamps

Plusieurs appels existent :

Code: Select all

utime()
utimes()
utimensat()
futimens()
`utime()` est l’ancienne interface simple.

Prototype :

Code: Select all

#include <utime.h>

int utime(const char *pathname, const struct utimbuf *buf);
Structure :

Code: Select all

struct utimbuf {
    time_t actime;   /* access time */
    time_t modtime;  /* modification time */
};
Exemple : mettre `atime` et `mtime` à l’heure actuelle.

Code: Select all

if (utime("file.txt", NULL) == -1) {
    perror("utime");
}
Exemple : définir explicitement les temps.

Code: Select all

struct utimbuf tb;

tb.actime = 1000000000;
tb.modtime = 1000000000;

if (utime("file.txt", &tb) == -1) {
    perror("utime");
}
Interfaces plus modernes :

Code: Select all

utimensat()
futimens()
Elles permettent une précision plus fine avec `struct timespec`.

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.
19.1. set-user-ID

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
Le `s` remplace le `x` côté propriétaire.

Exemple classique :

Code: Select all

/usr/bin/passwd
Un utilisateur normal doit pouvoir changer son mot de passe, mais le programme doit modifier des fichiers sensibles comme :

Code: Select all

/etc/shadow
Donc `passwd` peut être possédé par root avec le bit setuid.

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
C’est puissant, mais dangereux si le programme contient une faille.

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
Le `s` remplace le `x` côté groupe.

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/
Cela permet de garder une cohérence de groupe dans un répertoire partagé.

19.3. sticky bit

Le sticky bit est surtout utile sur les dossiers.

Exemple :

Code: Select all

drwxrwxrwt
Le `t` final indique le sticky bit.

Cas typique :

Code: Select all

/tmp
Tout le monde peut écrire dans `/tmp`, mais un utilisateur ne doit pas pouvoir supprimer les fichiers des autres.

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.
Commande :

Code: Select all

chmod +t /tmp
20. Notation octale des permissions

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
Découpage :

Code: Select all

0755
 7 = user  = rwx
 5 = group = r-x
 5 = other = r-x
Valeurs :

Code: Select all

r = 4
w = 2
x = 1
Donc :

Code: Select all

7 = 4 + 2 + 1 = rwx
6 = 4 + 2     = rw-
5 = 4 + 1     = r-x
4 = 4         = r--
0 = aucun droit
Avec les bits spéciaux, on peut avoir quatre chiffres.

Exemple :

Code: Select all

4755
Le premier chiffre `4` signifie setuid.

Exemples :

Code: Select all

4000 = setuid
2000 = setgid
1000 = sticky bit
Donc :

Code: Select all

4755 = setuid + rwxr-xr-x
2755 = setgid + rwxr-xr-x
1777 = sticky + rwxrwxrwx
21. Taille du fichier et blocs alloués

Dans `struct stat`, deux champs sont souvent confondus :

Code: Select all

st_size
st_blocks
`st_size` donne la taille logique du fichier en octets.

`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);
Un fichier peut avoir une grande taille logique mais peu de blocs réellement alloués. C’est le cas des fichiers creux, appelés sparse files.

Exemple conceptuel :

Code: Select all

début du fichier -> données
grand trou non alloué
fin du fichier -> données
Le trou existe logiquement, mais ne consomme pas forcément de place disque.

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);
Mais en pratique, pour beaucoup d’exercices, un buffer de 4096 octets est déjà raisonnable.

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
Lister les attributs :

Code: Select all

lsattr file.txt
Rendre un fichier immutable :

Code: Select all

sudo chattr +i file.txt
Retirer le flag immutable :

Code: Select all

sudo chattr -i file.txt
24. Le flag immutable

Le flag immutable empêche la modification, suppression ou renommage normal du fichier.

Exemple :

Code: Select all

sudo chattr +i important.conf
Ensuite, même avec des permissions classiques favorables, le fichier est protégé.

Pour le modifier à nouveau :

Code: Select all

sudo chattr -i important.conf
À retenir :

Code: Select all

+i = immutable
Ce n’est pas une protection magique contre un attaquant avec contrôle kernel ou accès physique complet, mais c’est une protection système de fichiers très utile contre les modifications accidentelles ou certaines erreurs d’administration.

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
Dans cet état, le fichier ne peut pas être réécrit librement. On peut seulement ajouter.

C’est utile pour certains logs.

Retirer le flag :

Code: Select all

sudo chattr -a log.txt
À retenir :

Code: Select all

+a = append only
26. Exemple complet : afficher les attributs principaux d’un fichier

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;
}
Compilation :

Code: Select all

gcc -Wall -Wextra -o fileattr fileattr.c
Utilisation :

Code: Select all

./fileattr fichier.txt
./fileattr /tmp
./fileattr lien_symbolique
27. Exemple : ajouter un droit sans écraser les autres

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;
}
Explication :

Code: Select all

sb.st_mode | S_IXUSR
signifie :

Code: Select all

garde les permissions existantes
ajoute le bit execute user
28. Exemple : retirer un droit sans toucher au reste

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;
}
Explication :

Code: Select all

~S_IWOTH
inverse le masque.

Puis :

Code: Select all

sb.st_mode & ~S_IWOTH
garde tous les bits sauf le bit d’écriture pour `other`.

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;
}
À retenir :

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.
30. Exemple : effet de umask()

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;
}
On demande :

Code: Select all

0666
Mais avec :

Code: Select all

umask(0022)
le fichier final sera généralement :

Code: Select all

0644
31. Résumé des API à connaître

Récupérer les attributs

Code: Select all

stat()
lstat()
fstat()
Tester le type de fichier

Code: Select all

S_ISREG()
S_ISDIR()
S_ISLNK()
S_ISFIFO()
S_ISSOCK()
S_ISCHR()
S_ISBLK()
Modifier les permissions

Code: Select all

chmod()
fchmod()
Modifier propriétaire/groupe

Code: Select all

chown()
lchown()
fchown()
Tester accessibilité

Code: Select all

access()
Modifier les timestamps

Code: Select all

utime()
utimes()
utimensat()
futimens()
Contrôler le masque de création

Code: Select all

umask()
Commandes shell associées

Code: Select all

ls -l
stat
chmod
chown
chgrp
umask
touch
lsattr
chattr
32. Résumé des pièges importants
  • `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.
33. Ce qu’il faut retenir pour un développeur système

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 ?
La mentalité à avoir :

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.
34. Mini fiche de révision

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
Conclusion

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.
À ton niveau, le plus important est de bien maîtriser :

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
Une fois ce chapitre compris, tu sais déjà faire une grosse partie de ce que font les outils comme :

Code: Select all

ls -l
stat
chmod
chown
touch
lsattr
Et surtout, tu comprends mieux comment Linux décide réellement :

Code: Select all

Est-ce que ce processus a le droit d'accéder à ce fichier ?

Who is online

Users browsing this forum: No registered users and 1 guest