Pirater un descripteur de fichier Linux

Vous avez entendu le terme descripteur de fichier (descripteur de socket) dans les programmes qui traitent les données sur le réseau et les programmes qui traitent les données dans des fichiers locaux sous Linux.

Il est également apparu comme un descripteur de socket dans Introduction à l'API Socket apprise en langage C, Partie 1 Server Edition.

La plupart des gens comprennent qu'une valeur entière est utilisée pour identifier un fichier prêt à interagir avec un processus, mais tout le monde ne sait pas si profondément au-delà de cela. C'est ça?

Bien sûr, tant que vous construisez une application sous Linux, vous n'avez pas besoin d'en savoir plus, et si vous pouvez correctement sélectionner et programmer l'API liée à CRUD en utilisant le descripteur de fichier, il n'y aura pas de problème en tant que programmeur. (Le problème avec les descripteurs de fichiers est que le processus a dépassé le nombre maximum de descripteurs de fichiers qu'il peut ouvrir, mais vous devez alors augmenter cette limite ou vraiment l'ouvrir autant. Nous examinerons la conception de l'application pour voir si cela devient possible.)

En langage C, cela ressemble à ce qui suit.

python


#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

int main (int argc, char* argv[]) {

    int fd; // File Descriptor

    fd = open("tmp.txt", O_RDONLY);
    if (fd < 0) {
        perror("open() failed.");
        exit(EXIT_FAILURE);
    }   

    printf("%d\n", fd); // 3

    close(fd);

    return EXIT_SUCCESS;
}

Le type entier fd est le descripteur de fichier. Si c'est normal, une valeur de 0 ou plus sera acquise.

De plus, je pense que les descripteurs de fichiers sont généralement enveloppés par des langages de programmation et des bibliothèques, et que les CRUD sont échangés sous une structure de données de contrôle plus efficace.

Dans les cas suivants, fopen ajuste le nombre d'appels d'appels système et d'autres mesures sont prises pour permettre un traitement d'E / S efficace, donc je pense que ces API sont généralement utilisées. D'autres langages de programmation ont également de telles API, et j'écris souvent PHP, mais PHP a également une fonction utilitaire appelée fopen.

python


#include <stdio.h>
#include <stdlib.h>

int main (int argc, char* argv[]) {

    FILE *fp; // File pointer

    fp = fopen("tmp.txt", "r");
    if (fp == NULL) {
        fprintf(stderr, "fopen() failed.\n");
        exit(EXIT_FAILURE);
    }   

    printf("%d\n", fp->_fileno); // 3

    fclose(fp);

    return EXIT_SUCCESS;
}

Linux (Unix) est un système qui sublime tout dans le système dans le concept abstrait de fichiers. Le concept général du système de fichiers de Linux n'est pas trop compliqué à aborder pendant des jours, il est si simple qu'il est facile d'en parler.

Si les programmeurs ainsi que les utilisateurs généraux peuvent saisir les nuances du concept abstrait des fichiers, une interface leur est fournie qui leur permet de tout faire fonctionner de manière intuitive et égale.

Le descripteur de fichier est ensuite utilisé comme identifiant pour accéder à l'entité du fichier abstrait.

Je pensais que si je pouvais en savoir un peu plus sur le descripteur de fichier qui est à la base du système, je pourrais mieux utiliser le système appelé Linux, alors je l'ai étudié.

Cependant, je ne sais pas comment enquêter même si cela s'appelle vaguement un descripteur de fichier, j'ai donc décidé d'étudier l'appel système qui y est lié.

piratage d'appel système ouvert

Je vais l'explorer en suivant la série de processus d'ouverture qui sont sortis plus tôt. Vous pouvez obtenir le descripteur de fichier à la suite de l'ouverture, donc si vous suivez ce processus, vous devriez savoir quelque chose.

python


 415879:   b8 02 00 00 00          mov    $0x2,%eax
 41587e:   0f 05                   syscall 

Désassemblons et vérifions rapidement le traitement de la fonction __libc_open de la glibc correspondant à la fonction open. Le numéro d'appel du système ouvert est le 2.

include/asm-x86_64/unistd.h


#define __NR_open                                2
__SYSCALL(__NR_open, sys_open)

Vous pouvez voir que la fonction correspondante dans la table sys_call_table est sys_open.

Maintenant, suivons le processus de sys_open.

fs/open.c(933-941)


asmlinkage long sys_open(const char __user * filename, int flags, int mode)
{
    char * tmp; 
    int fd, error;

#if BITS_PER_LONG != 32
    flags |= O_LARGEFILE;
#endif

Tout d'abord, pour les machines 64 bits, activez automatiquement l'indicateur O_LARGEFILE. Cela vous permettra de gérer des fichiers de plus de 2 Go. De nos jours, sur les machines 64 bits, vous n'avez pas à vous en soucier, mais vous devez faire attention lors de la mise en œuvre du processus de gestion des fichiers sur des machines 32 bits dans votre application. En passant, j'ai entendu dire que vous ne pouvez pas ouvrir des fichiers de plus de 2 Go avec des modules dont le développement s'est arrêté à l'ère des machines 32 bits, mais c'est parce que vous n'avez pas spécifié cet indicateur.

fs/open.c(941)


    tmp = getname(filename);

Utilisez la fonction getname pour transférer l'objet contenant le nom de fichier de l'espace utilisateur vers l'espace noyau. La zone mémoire est allouée à l'aide de l'allocateur de dalle. L'allocateur de slab est l'une des méthodes utilisées par Linux pour une allocation mémoire efficace. Je voudrais bientôt me plonger dans l'allocateur de dalles.

fs/open.c(942-944)


    fd = PTR_ERR(tmp);
    if (!IS_ERR(tmp)) {
        fd = get_unused_fd();

S'il n'y a pas d'erreur, exécutez la fonction get_unused_fd pour trouver un descripteur de fichier gratuit. À en juger par le nom de la fonction, il semble que ce processus vous dira quel est le descripteur de fichier.

fs/open.c(838-845)


int get_unused_fd(void)
{
    struct files_struct * files = current->files;
    int fd, error;

    error = -EMFILE;
    spin_lock(&files->file_lock);

J'ai également expliqué au moment de l'appel système fork, mais le courant est un pointeur vers le descripteur de processus (structure task_struct) du processus en cours d'exécution sur le processeur actuel. Est une macro à obtenir.

current-> files contient un pointeur vers la structure files_struct, qui contient des informations sur les fichiers ouverts par le processus en cours.

La structure files_struct a la structure suivante. N'oubliez pas que vous devrez vérifier à nouveau ici à la fin.

include/linux/file.h


/*
 * Open file table structure
 */
struct files_struct {
        atomic_t count;
        spinlock_t file_lock;     /* Protects all the below members.  Nests inside tsk->alloc_lock */
        int max_fds;
        int max_fdset;
        int next_fd;
        struct file ** fd;      /* current fd array */
        fd_set *close_on_exec;
        fd_set *open_fds;
        fd_set close_on_exec_init;
        fd_set open_fds_init;
        struct file * fd_array[NR_OPEN_DEFAULT];
};

files-> open_fds est un pointeur vers la structure fd_set files-> open_fds_init, et files-> open_fds_init représente le descripteur de fichier actuellement ouvert sous la forme d'un bitmap 64x16 = 1024. Voici un extrait de la justification du calcul que j'ai effectué.

include/linux/posix_types.h


#define __NFDBITS   (8 * sizeof(unsigned long))
#define __FD_SETSIZE    1024
#define __FDSET_LONGS   (__FD_SETSIZE/__NFDBITS)

typedef struct {
    unsigned long fds_bits [__FDSET_LONGS];
} __kernel_fd_set;

Normalement, 1024 bits suffiront, mais si ce n'est pas suffisant, il sera développé par la fonction expand_files. C'est lié à l'histoire dont j'ai parlé au début, à savoir que si vous avez moins de processus d'ouverture, vous pouvez augmenter la limite.

fs/open.c(847)


    fd = find_next_zero_bit(files->open_fds->fds_bits,
                files->max_fdset,
                files->next_fd);

À partir de là, la fonction find_next_zero_bit recherche et obtient des bits libres.

fs/open.c(872-874)


    FD_SET(fd, files->open_fds);
    FD_CLR(fd, files->close_on_exec);
    files->next_fd = fd + 1; 

Ajoute un nouveau descripteur de fichier à l'ensemble des descripteurs de fichier ouverts et le supprime de l'ensemble des descripteurs qui sont fermés pendant exec ().

Vous pouvez voir que ce processus présente un avantage dans la communication avec les processus enfants. Il est également lié au traitement des tuyaux qui réalisent la communication inter-processus.

Ensuite, mettez à jour le membre next_fd avec le nombre maximal de descripteurs de fichier attribués plus un. Il sera utilisé pour la prochaine analyse du descripteur de fichier.

Eh bien, j'ai essayé d'obtenir le descripteur de fichier, mais je ne peux toujours pas voir le descripteur de fichier réel. Pourtant, j'ai l'impression que je viens de recevoir un descripteur de fichier gratuit comme clé pour le moment.

Il revient au traitement de sys_open à nouveau. fd est renvoyé en tant que descripteur de fichier.

fs/open.c(945-951)


        if (fd >= 0) { 
            struct file *f = filp_open(tmp, flags, mode);
            error = PTR_ERR(f);
            if (IS_ERR(f))
                goto out_error;
            fd_install(fd, f);
        }    

Exécutez la fonction filp_open pour obtenir l'adresse de la structure de fichier obtenue en fonction du chemin, du mode d'accès et des bits d'autorisation du fichier passés en arguments.

La structure des fichiers est la suivante. Puisque nous avons tous les membres importants, je voudrais faire un article différent juste pour la structure des fichiers.

include/linux/fs.h


struct file {
    struct list_head    f_list;
    struct dentry       *f_dentry;
    struct vfsmount         *f_vfsmnt;
    struct file_operations  *f_op;
    atomic_t        f_count;
    unsigned int        f_flags;
    mode_t          f_mode;
    int         f_error;
    loff_t          f_pos;
    struct fown_struct  f_owner;
    unsigned int        f_uid, f_gid;
    struct file_ra_state    f_ra;

    size_t          f_maxcount;
    unsigned long       f_version;
    void            *f_security;

    /* needed for tty driver, and maybe others */
    void            *private_data;

#ifdef CONFIG_EPOLL
    /* Used by fs/eventpoll.c to link all the hooks to this file */
    struct list_head    f_ep_links;
    spinlock_t      f_ep_lock;
#endif /* #ifdef CONFIG_EPOLL */
    struct address_space    *f_mapping;
};

Maintenant, suivons le traitement de la fonction filp_open.

fs/open.c(753-762)


struct file *filp_open(const char * filename, int flags, int mode)
{
    int namei_flags, error;
    struct nameidata nd;

    namei_flags = flags;
    if ((namei_flags+1) & O_ACCMODE)
        namei_flags++;
    if (namei_flags & O_TRUNC)
        namei_flags |= 2;

Définissez le mode d'accès de manière appropriée pour namei_flags. Notez que nous convertissons les drapeaux de namei_flags dans un format spécial.

En binaire, 00 (lecture seule) va à 01, 01 (écriture seule) va à 10 et 10 (lecture et écriture uniquement) va à 11. En d'autres termes, si 0 bit est défini, cela signifie lecture, et si 1 bit est défini, cela signifie écriture.

Cet indicateur converti sera utilisé pour un traitement ultérieur.

fs/open.c(764)


    error = open_namei(filename, namei_flags, mode, &nd);

La fonction open_namei gère la partie importante de l'ouverture réelle du fichier. En tant qu'arguments, transmettez le nom du fichier, les indicateurs de mode d'accès convertis, les bits d'autorisation et un pointeur vers la structure nameidata.

La structure nameidata est définie comme ci-dessous.

include/linux/namei.h


struct nameidata {
    struct dentry   *dentry;
    struct vfsmount *mnt;
    struct qstr last;
    unsigned int    flags;
    int     last_type;
    unsigned    depth;
    char *saved_names[MAX_NESTED_LINKS + 1]; 

    /* Intent data */
    union {
        struct open_intent open;
    } intent;
};

C'est aussi une structure importante, alors j'aimerais prendre un peu de temps pour y creuser, mais ce à quoi je dois faire attention maintenant, c'est l'état du membre denté, qui est un pointeur vers la structure dentée, et le système de fichiers monté sur le système. Un membre mnt qui est un pointeur vers la structure vfsmount qui enregistre.

La tâche à l'intérieur de la fonction open_namei est d'obtenir l'objet de la structure nameidata en fonction du chemin et des indicateurs.

La partie principale du traitement se fait dans une autre fonction appelée path_lookup, mais la méthode de recherche est finement ajustée en fonction du mode d'accès, et du fs qui stocke les informations du système de fichiers associé au processus à partir du descripteur de processus actuel Le processus de recherche est lancé sur la base des informations.

En conséquence, la structure nameidata contient les données du résultat de la recherche par nom de chemin.

fs/open.c(764-768)


    if (!error)
        return dentry_open(nd.dentry, nd.mnt, flags);

    return ERR_PTR(error);
}

À ce stade, vous avez un pointeur vers la structure dentry pour le chemin d'accès et un pointeur vers la structure vfsmount. La fonction dentry_open crée un objet fichier basé sur les deux informations et les indicateurs convertis.

fs/open.c(945-951)


        if (fd >= 0) { 
            struct file *f = filp_open(tmp, flags, mode);
            error = PTR_ERR(f);
            if (IS_ERR(f))
                goto out_error;
            fd_install(fd, f);
        }    

Stockez le pointeur vers l'objet fichier obtenu par la fonction dentry_open dans la variable f. Et s'il n'y a pas d'erreurs, nous passons un pointeur vers le descripteur de fichier et l'objet fichier vers la fonction fd_install, qui est le cœur du descripteur de fichier.

fs/open.c(945-951)


void fastcall fd_install(unsigned int fd, struct file * file)
{
    struct files_struct *files = current->files;
    spin_lock(&files->file_lock);
    if (unlikely(files->fd[fd] != NULL))
        BUG();
    files->fd[fd] = file;
    spin_unlock(&files->file_lock);
}

Le membre du descripteur de processus courant (files_struct *) Le membre de files (fd_set *) fd stocke le pointeur vers l'objet fichier obtenu précédemment par la fonction filp_open.

Ce membre fd est un pointeur vers un tableau de pointeurs vers l'objet fichier (struct file **), mais les entiers dans le descripteur de fichier correspondent aux indices de ce tableau.

À propos, fd pointe vers le membre fd_array dans la même structure, mais dans le cas d'une machine 64 bits, si le nombre de descripteurs de fichier dépasse 64, une nouvelle zone sera réservée et pointera vers cette adresse.

En d'autres termes, files-> fd [0] pointe vers le descripteur de fichier 0 et files-> fd [1] pointe vers l'objet fichier du descripteur de fichier 1.

Comme on le sait, 0 correspond généralement à l'entrée standard, 1 correspond à la sortie standard et 2 correspond à la sortie d'erreur standard, de sorte que le descripteur du premier fichier ouvert par l'utilisateur doit être 3. Même le programme créé dans la première démo s'affiche sous la forme 3.

Alors, qu'est-ce qu'un descripteur de fichier sous Linux? Si vous répondez à propos de

__ Indice du tableau contenant l'objet fichier ouvert au processus en cours __

On peut le dire.

C'était un peu difficile à comprendre avec des mots, donc intuitivement c'est comme suit.

python


//[fd]Est un descripteur de fichier
current->files->fd[fd]

Ensuite, l'opération sur le fichier ouvert est effectuée en accédant aux informations de l'objet fichier à l'aide de l'indice du membre fd.

L'objet fichier a un membre f_pos qui enregistre le décalage par rapport aux octets transférés, vous pouvez donc continuer le traitement du fichier en mettant à jour cette valeur avec le nombre d'octets transférés. L'opération elle-même est effectuée à l'aide des fonctions d'opération de fichier (lecture, écriture, etc.) définies dans le membre f_op de l'objet fichier.

En passant, à partir de 2.6.14, une nouvelle structure fdtable a été créée et la structure files_struct a changé en conséquence. La structure ci-dessous a peu à peu changé, mais la structure sous-jacente reste la même jusqu'à Linux 4.9.

c:Linux2.6.14


struct fdtable {
    unsigned int max_fds;
    int max_fdset;
    int next_fd;
    struct file ** fd;      /* current fd array */
    fd_set *close_on_exec;
    fd_set *open_fds;
    struct rcu_head rcu;
    struct files_struct *free_files;
    struct fdtable *next;
};

struct files_struct {
    atomic_t count;
    struct fdtable *fdt;
    struct fdtable fdtab;
    fd_set close_on_exec_init;
    fd_set open_fds_init;
    struct file * fd_array[NR_OPEN_DEFAULT];
    spinlock_t file_lock;     /* Protects concurrent writers.  Nests inside tsk->alloc_lock */
};

void fastcall fd_install(unsigned int fd, struct file * file)
{
    struct files_struct *files = current->files;
    struct fdtable *fdt;
    spin_lock(&files->file_lock);
    fdt = files_fdtable(files);
    BUG_ON(fdt->fd[fd] != NULL);
    rcu_assign_pointer(fdt->fd[fd], file);
    spin_unlock(&files->file_lock);
}

Par conséquent, au lieu de référencer le membre fd directement à partir de la structure files_struct, le flux consiste à faire référence à fd (un pointeur vers un tableau de pointeurs vers l'objet fichier) en passant par le membre fdt une fois.

Descripteur de fichier à usage général

Maintenant, allons plus loin en ce qui concerne les descripteurs de fichiers.

Au début, j'ai mentionné que les programmes qui traitent les données sur le réseau sous Linux et les programmes qui traitent les données dans des fichiers locaux utilisent des descripteurs de fichiers (descripteurs de socket).

Introduction à l'API Socket apprise en langage C 3e édition serveur / client # 1 utilise des API de socket telles que recv et send, mais ce processus est lu. Il peut être complètement remplacé par une API d'entrée / sortie de base telle que ou write.

C'est parce que ce que socket obtient est un descripteur de fichier et que l'entité à laquelle il peut accéder n'est rien d'autre qu'un objet fichier. Ce qui suit est un extrait partiel du processus d'ouverture d'un socket.

net/socket.c(377)


struct file *file = get_empty_filp();

Obtenez un pointeur vers un nouvel objet fichier avec la fonction get_empty_filp. Cette fonction alloue également une zone de mémoire pour l'objet fichier par l'allocateur de dalle.

net/socket.c(402-407)


        sock->file = file;
        file->f_op = SOCK_INODE(sock)->i_fop = &socket_file_ops;
        file->f_mode = FMODE_READ | FMODE_WRITE;
        file->f_flags = O_RDWR;
        file->f_pos = 0; 
        fd_install(fd, file);

Définissez divers paramètres pour l'objet fichier acquis, et enfin fd_install. C'est la même chose que l'appel système ouvert.

Ce ne sont pas que des prises. Même le traitement des tuyaux. Ce qui suit est un extrait partiel du traitement de l'appel de système de canalisations.

fs/pipe.c(402-407)


    f1 = get_empty_filp();
    if (!f1)
        goto no_files;

    f2 = get_empty_filp();
    if (!f2)
        goto close_f1;

f1 est l'objet fichier à lire et f2 est l'objet fichier à écrire.

fs/pipe.c(760-774)


    /* read file */
    f1->f_pos = f2->f_pos = 0;
    f1->f_flags = O_RDONLY;
    f1->f_op = &read_pipe_fops;
    f1->f_mode = FMODE_READ;
    f1->f_version = 0;

    /* write file */
    f2->f_flags = O_WRONLY;
    f2->f_op = &write_pipe_fops;
    f2->f_mode = FMODE_WRITE;
    f2->f_version = 0;

    fd_install(i, f1);
    fd_install(j, f2);

Vous pouvez voir qu'il s'agit du même processus que open car divers paramètres sont définis pour chaque objet fichier et la fin est toujours fd_install.

Comme vous pouvez le voir, le descripteur de fichier joue un rôle très important dans le système appelé Linux, qui essaie de tout faire fonctionner avec des fichiers. Au cas où, je lis aussi le code Linux 4.7 actuellement stable, mais ce principe de traitement n'a pas changé, et cette idée est toujours vivante.

Je voudrais examiner de plus près les différentes structures de données et processus que je n'ai pas beaucoup expliqués cette fois, même en explorant le système de fichiers séparément.

Code source référencé

Noyau Linux: 2.6.11 glibc:glibc-2.12.1 CPU:x86_64

Recommended Posts

Pirater un descripteur de fichier Linux
[Linux] Recherche de fichiers
Périphérique et système de fichiers Linux
À propos des autorisations de fichiers et de répertoires Linux
Pirater les appels système de Linux
Session d'étude Linux 2ème: Fonctionnement des fichiers
[Linux] Commandes Linux fréquemment utilisées (opération sur fichier)
Linux
[Linux] Commandes d'opération de fichier et de répertoire
[NFS] Partage de fichiers entre hôtes Linux
Construction du serveur de fichiers Linux (Ubuntu et Samba)
Renommer en fonction de l'heure de modification du fichier (Linux)
Périphérique Ping-t (sujet 104), système de fichiers Linux, FSH