web-dev-qa-db-fra.com

getline () vs. fgets (): Contrôle l'allocation de mémoire

Pour lire les lignes d'un fichier, il y a les fonctions getline() et fgets() POSIX (en ignorant les redoutables gets()). Il est logique que getline() soit préféré à fgets() car il alloue le tampon de ligne selon les besoins.

Ma question est: N'est-ce pas dangereux? Que se passe-t-il si par accident ou intention malveillante quelqu'un crée un fichier de 100 Go sans octet '\n' - cela ne fera-t-il pas que mon appel getline() allouera une quantité insensée de mémoire?

23
edavid

Ma question est: N'est-ce pas dangereux? Que se passe-t-il si par accident ou intention malveillante quelqu'un crée un fichier de 100 Go sans octet "\ n" - ne fera-t-il pas que mon appel getline () alloue une quantité folle de mémoire?

Oui, ce que vous décrivez est un risque plausible. cependant,

  • si le programme nécessite de charger une ligne entière en mémoire à la fois, alors permettre à getline() d'essayer de le faire n'est pas intrinsèquement plus risqué que d'écrire votre propre code pour le faire avec fgets(); et
  • si vous avez un programme qui présente une telle vulnérabilité, vous pouvez atténuer le risque en utilisant setrlimit() pour limiter la quantité totale de mémoire (virtuelle) qu'il peut réserver. Cela peut être utilisé pour provoquer son échec au lieu d'allouer avec succès suffisamment de mémoire pour interférer avec le reste du système.

Le mieux dans l'ensemble, je dirais, est d'écrire du code qui ne nécessite pas d'entrée en unités de lignes complètes (en une seule fois) en premier lieu, mais une telle approche a ses propres complexités.

17
John Bollinger

Cela peut être dangereux, oui. Je ne sais pas comment cela fonctionnerait sur d'autres ordinateurs, mais l'exécution du code ci-dessous a gelé mon ordinateur au point d'avoir besoin d'une réinitialisation matérielle:

/* DANGEROUS CODE */

#include <stdio.h>

int main(void)
{
    FILE *f;
    char *s;
    size_t n = 0;

    f = fopen("/dev/zero", "r");
    getline(&s, &n, f);

    return 0;
}
11
Tom Zych

La fonction getline utilise malloc et realloc en interne et renvoie -1 en cas d'échec, le résultat n'est donc pas différent de celui que vous avez tenté d'appeler malloc(100000000000) . À savoir, errno est défini sur ENOMEM et getline renvoie -1.

Vous auriez donc le même problème si vous utilisiez getline ou tentiez de faire la même chose avec fgets et l'allocation de mémoire manuelle pour vous assurer de lire une ligne complète.

3
dbush

Certaines directives de codage (comme MISRA C) peuvent vous empêcher d'utiliser des allocations dynamiques de mémoire (comme getline()). Il y a des raisons à cela, par exemple en évitant les fuites de mémoire.

Si vous connaissez la taille maximale de toutes les lignes acceptables, vous pouvez éviter les allocations de mémoire en utilisant fgets() au lieu de getline(), et ainsi supprimer un point de fuite mémoire potentiel.

1
SKi

Cela dépend vraiment de la façon dont vous voulez gérer les lignes trop longues.

fgets avec un tampon de taille décente fonctionnera généralement, et vous pouvez détecter qu'il a "échoué" - la fin du tampon n'a pas de caractère de nouvelle ligne. Il est possible d'éviter de toujours faire un strlen () pour confirmer si le tampon est débordé, mais c'est une question différente.

Peut-être que votre stratégie consiste simplement à ignorer les lignes qui ne peuvent pas être traitées, ou peut-être que le reste de la ligne n'est qu'un commentaire que vous ignoreriez de toute façon, auquel cas, il est facile de mettre ensuite fgets dans une boucle pour éliminer le reste de la ligne sans pénalité d'allocation.

Si vous voulez lire la ligne entière, alors getline peut être la meilleure stratégie pour vous. L'utilisateur malveillant aurait besoin de beaucoup d'espace disque pour provoquer le mauvais comportement que vous décrivez, ou peut-être passer/dev/random ou similaire comme nom de fichier d'entrée.

Encore une fois, si getline ne peut pas être réaffecté, il échouera de manière à ce que vous puissiez récupérer, bien que si vous réutilisez le tampon pour plusieurs lectures de ligne, vous souhaiterez peut-être libérer le tampon qu'il possède après un erreur avant d'essayer de lire davantage, car elle est toujours allouée et peut avoir augmenté autant qu'elle le pouvait avant d'échouer.

0
Gem Taylor

getline() réaffectez le tampon pour vous permettre d'alléger un peu la gestion de la mémoire dans votre programme.

Mais en effet, cela pourrait entraîner l'allocation d'une grande partie de la mémoire. Si c'est un problème, vous devez prendre des mesures supplémentaires pour utiliser des fonctions qui n'allouent pas de mémoire implicitement.

0