Sur internet, on voit pas mal de chose. Récemment j'ai pu lire que le C était un langage facile. Si on peut s'habituer à la syntaxe relativement vite, produire un code portable et sécurisé est en revanche une autre paire de manche. Sceptique ? Combien de personnes vident le flux écran après affichage à l'écran sans '\n' ? Combien de personnes utilisent scanf ou fgets sans contrôle du retour ou sans vider le buffer clavier ? Probablement beaucoup, voire la majorité. La faute au facteur temps ou pire l'ignorance.

printf

Avant de fixer la règle, il faut savoir que printf envoie des caractères au buffer écran. Ce dernier se vide automatiquement lorsqu'il est rempli ou lorsqu'il y a un '\n'. Donc, l'instruction :

printf("Bonjour");

ne forcera pas l'affichage de "Bonjour" à l'écran. Il faut utiliser :

printf("Bonjour");
fflush(stdout);

pour vider le flux.
A noter que printf("Bonjour\n"); ou puts("Bonjour"); n'ont pas besoin de vidage du flux car il y a envoi du '\n' dans le flux sortie.

scanf, fgets

char buf[10];
scanf("%s",s);

est à proscrire et pour simple raison, il n'y a pas de contrôles sur la longueur maximale de la chaîne.

scanf("%9s",s);

serait déjà beaucoup mieux. Mais que se passe-t-il si la longueur est variable ? Non, on ne peut pas mettre de variables à la place du 9. Dans ce cas, il faut utiliser des macros, ce qui complique le code. Sans penser que certains oublieront d'imposer la taille dans le scanf.
Donc, il est mieux de ne pas se poser de questions, et d'utiliser :

fgets(buf,sizeof buf,stdin);

Ici, il y a contrôle la taille, et utilisation d'une variable pour la longueur. Sauf que voilà, ça ne suffit pas. Tout caractère passé au-delà de la taille sera ajouté au buffer. Faut donc vider ce dernier. Pour le vider, il faut utiliser :

int c; 
while((c=getchar())!='\n' && c!=EOF);

et non :

fflush(stdin);

dont la fonctionnalité est incertaine.
Ce qui donne comme code :

int c;
char buf[10];
fgets(buf,sizeof(buf),stdin);
while((c=getchar())!='\n' && c!=EOF);

Oui, mais si l'utilisateur entre une chaîne trop petite, le '\n' va se stocker dans buf et du coup, boucle infinie avec while. Donc, il faut tester soit la longueur de la chaîne entrée par l'utilisateur soit rechercher le '\n' pour en déduire si le '\n' est dans le buffer clavier ou pas. On va privilégier la deuxième technique parce qu'en plus, on pourra virer le '\n' stocké dans la chaîne.
Et puis, n'oublions pas qu'il faut vérifier la sortie de fgets pour détecter une erreur. Au final, voici le code, portable et sécurisé.

int c;
char *pbuf;
if( fgets(buf,sizeof(buf),stdin) != NULL ) {
    if( (p=strchr(buf,'\n')) != NULL )
        *p='\0';
    else
        while( (c=getchar()) != '\n' && c != EOF );
}


Bien sûr, pour les entrées de nombres, c'est pareil. N'utilisez pas scanf("%d",&a); car si l'utilisateur entre des lettres à la place, scanf va sauter. Donc, il vaut mieux utiliser fgets puis traiter la chaîne (strtol par exemple) pour récupérer le nombre.

Et si, je fais pas de contrôle ?

Alors bien sûr, certains vont se dire que ce code, c'est pur parano. Eh bien non, car en manquement de ceci, voici ce qui pourrait se produire.
Regardons le code :

int toto=5;
char buf[10];
scanf("%s",buf);
printf("%d",toto);


Si l'utilisateur entre une chaîne de dix 'a' par exemple, le dernier 'a' s'écrira dans la chaîne. Quid du '\0' ? Il débordera. Donc toto va devenir égal à 0 (l '\0' a débordé).

Imaginez-vous maintenant qu'un pirate utilise votre code (qui a le bit setuid d'activé), et qui mette une chaîne trop grande aussi. Très rapidement, il va tomber sur l'eip (registre contenant l'adresse de la prochaine instruction à exécuter). Il n'a plus qu'à modifier cette adresse vers une autre en mémoire contenant un joli shellcode (en faisant attention au boutisme) pour que la machine exécute le code malveillant avec les droits root. Au final, le pirate peut faire exécuter ce qu'il veut. Je ne rentrerais pas plus dans les détails de cette attaque, le but étant juste de vous démontrer qu'il est important de sécuriser son code

Note pour ceux qui ont testé de récrire sur l'EIP

Il est fort probable que ceux qui testent reçoivent : *** stack smashing detected ***. Normal, les compilateurs connaissent maintenant ces attaques (bufferOverflow) et rajoutent quelques octets de sécurité (le canary). Si ces octets ont été modifiés, c'est qu'il y a eu débordements en mémoire, et on affiche le joli message. Pour tester, il faut compiler avec -fno-stack-protector sur gcc.

Conclusion

Vous pouvez vous dire que le fameux canary peut régler cette attaque. Sauf qu'on veut du code portable, et que tous les compilateurs n'ont pas cette fonctionnalité. Et puis, une sécurité ça se passe. Bien qu'elle soit assez efficace, dans certains cas, on peut écrire au-delà du canary sans l'altérer. Et puis, si l'application est sur un serveur et qu'on compte sur la protection du compilateur, une personne malveillante pourrait facilement faire crasher le programme, provoquant un joli déni de service.

Si on applique, et il faut, tous ses éléments et je n'ai pas été exhaustif, le C n'est plus si facile que ça, voire même carrément difficile. Heureusement, que la plupart des autres langages ne sont pas aussi critiques. Les autres langages ont souvent un type string qui s'occupe de faire tout le sale travail à votre place ou même l'utilisation d'exceptions permettant de continuer le code au lieu de le faire planter