Serveur HTTP et Stack smashing detected

Serveur HTTP et Stack smashing detected - C - Programmation

Marsh Posté le 14-10-2007 à 16:40:29    

Salut à tous,
 
Je dois réaliser un petit serveur HTTP en TP cette année, et je me retrouve complètement bloqué face à une erreur que je n'avais jamais rencontrée auparavant.
 
D'après gdb, le problème se situe à la sortie de manageRequest ( ligne 265 ), mais je n'ai pas réussi à lui soutirer plus d'informations.
 
De plus, le problème ne survient que lors de l'appel à la fonction execCGI, même si le programme ne plante pas directement dans cette fonction. Dans le cas d'une demande d'un fichier html ou d'une image, tout se passe normalement et le thread se termine sans soucis.
 
Si cela peut aider certains d'entre vous, voici le code source complet ainsi que les fichiers qui me servent à tester le serveur : http://sebxoiii.free.fr/Programmation/httpServer.zip
 
Merci d'avance.
 

Code :
  1. /*--------------------------------------------------------------------------*/
  2. #include "netUtils.h"
  3. /*--------------------------------------------------------------------------*/
  4. #define BUFFER_SIZE 0x1000
  5. typedef struct {
  6. int extFd;
  7. SSL * ssl;
  8. int intFd;
  9. struct
  10. {
  11.  char buffer[BUFFER_SIZE];
  12.  char * ptr;
  13.  unsigned int remaining;
  14. } incoming,outgoing;
  15. char requestMethod[0x100];
  16. char requestUri[0x100];
  17. char contentLength[0x100];
  18. char fileName[0x100];
  19. } Request;
  20. Request *createRequest(int fd,SSL_CTX * ctx)
  21. {
  22. Request * req=(Request *)malloc(sizeof(Request));
  23. req->extFd=fd;
  24. req->ssl=(SSL *)0;
  25. req->intFd=-1;
  26. req->incoming.buffer[0]='\0';
  27. req->incoming.ptr=(char *)0;
  28. req->incoming.remaining=0;
  29. req->outgoing.buffer[0]='\0';
  30. req->outgoing.ptr=(char *)0;
  31. req->outgoing.remaining=0;
  32. req->requestMethod[0]='\0';
  33. req->requestUri[0]='\0';
  34. req->contentLength[0]='\0';
  35. req->fileName[0]='\0';
  36. if(ctx)
  37. {
  38.  /* ... A COMPLETER ... : initialiser req->ssl */
  39. }
  40. return req;
  41. }
  42. void destroyRequest(Request * req)
  43. {
  44. int r;
  45. if(req->ssl)
  46. {
  47. /* ... A COMPLETER ... : detruire req->ssl */
  48. }
  49. RESTART_SYSCALL(r,close(req->extFd));
  50. if(req->intFd!=-1)
  51. {
  52.  RESTART_SYSCALL(r,close(req->intFd));
  53. }
  54. free(req);
  55. }
  56. int requestRead(Request * req,void * buffer,unsigned int size)
  57. {
  58. return req->ssl ? errno=EBADF,-1 /* ... A COMPLETER ... : lire req->ssl */
  59.                 : read(req->extFd,buffer,size);
  60. }
  61. int requestWrite(Request * req,const void * buffer,unsigned int size)
  62. {
  63. return req->ssl ? errno=EBADF,-1 /* ... A COMPLETER ... : ecrire req->ssl */
  64.                 : write(req->extFd,buffer,size);
  65. }
  66. MAKE_READLINE_FUNCTION(requestReadLine,requestRead,Request *)
  67. MAKE_WRITEFULLY_FUNCTION(requestWriteFully,requestWrite,Request *)
  68. /*--------------------------------------------------------------------------*/
  69. void contentType(Request *req, char *type)
  70. {
  71. char *buffer=req->fileName;
  72. unsigned int i=0;
  73. /* Détection du point qui précède l'extension. */
  74. do
  75. {
  76.  i++;
  77. }
  78. while(buffer[i]!='.' && i!=strlen(buffer));
  79. /* Une fois le point détecté, modification de l'adresse du début du buffer avant qu'elle coincide avec celle du début de l'extension. */
  80. buffer=&buffer[i+1];
  81. /* Il ne reste ensuite plus qu'à comparer le chaine "buffer" qui ne contient désormais plus que l'extension avec les extensions courantes.*/
  82. if(strcmp(buffer,"jpg" )==0)   {strcpy(type,"image/jpeg" );}
  83. else if(strcmp(buffer,"gif" )==0) {strcpy(type,"image/gif" );}
  84. else if(strcmp(buffer,"c" )==0)  {strcpy(type,"text/plain" );}
  85. else if(strcmp(buffer,"txt" )==0) {strcpy(type,"text/plain" );}
  86. else if(strcmp(buffer,"html" )==0) {strcpy(type,"text/html" );}
  87. else if(strcmp(buffer,"bin" )==0) {strcpy(type,"application/octet-stream" );}
  88. /*else if(strcmp(buffer,"cgi" )==0) {strcpy(type,"cgi" );}*/ /*Si CGI ou autre exécutable le test est réalisé au niveau de access. */
  89. else        {strcpy(type,"application/octet-stream" );}
  90. }
  91. void suppressionParametresCGI(Request *req,char *parametresCGI)
  92. {
  93. unsigned int i=0;
  94. /* Detection du premier point d'interrogation. */
  95. do
  96. {
  97.  i++;
  98. }
  99. while(req->fileName[i]!='?' && i!=strlen(req->fileName));
  100. /* Sauvegarde des paramètres CGI. */
  101. strcpy(parametresCGI,&(req->fileName[i]));
  102. /* Suppression des parametres CGI du champ fileName. */
  103. req->fileName[i]='\0';
  104. }
  105. void execCGI(Request *req)
  106. {
  107. int fdCGI[2],r=0;
  108. pid_t result;
  109. r=pipe(fdCGI); /* Création d'un tube de communication : 2 file descriptors (un en écriture, un en lecture). */
  110. if(r==-1){ perror("pipe" ); exit(EXIT_FAILURE); }
  111. result=fork(); /* Création d'un nouveau processus. */
  112. switch(result)
  113. {
  114.  case -1: perror("fork" ); exit(EXIT_FAILURE);
  115.  /* Processus enfant :*/
  116.  case 0:  close(fdCGI[0]); /* Fermeture de l'un des file descriptors. */
  117.     dup2(fdCGI[1],STDOUT_FILENO); /* Inversion de l'entrée standard et du descripteur de fichier créé en écriture. */
  118.     execlp(req->fileName,req->fileName,NULL); /* Remplacement du processus courant par un processus lançant le script cgi. */
  119.  /* Processus parent :*/
  120.  default: close(fdCGI[1]); /* Fermeture de l'un des file descriptors. */
  121.     do /* Boucle utilisée pour lire le code HTML créé par le script CGI et envoyé par le tube. */
  122.     {
  123.      req->outgoing.remaining=readLine(fdCGI[0],req->outgoing.buffer,BUFFER_SIZE);
  124.      req->outgoing.remaining=requestWriteFully(req,req->outgoing.buffer,req->outgoing.remaining);
  125.     } while (req->outgoing.remaining != 0);
  126.     close(fdCGI[0]); /* Fermeture du file descriptor restant. */
  127. }
  128. }
  129. void envoiBuffer(Request *req)
  130. {
  131. req->outgoing.remaining=requestWriteFully(req,req->outgoing.buffer,strlen(req->outgoing.buffer));
  132. }
  133. void badRequest(Request *req)
  134. {
  135. strcpy(req->outgoing.buffer,"HTTP/1.1 400 Bad Request\n" );
  136. req->outgoing.remaining=strlen(req->outgoing.buffer);
  137. envoiBuffer(req);
  138. }
  139. void okRequest(Request *req)
  140. {
  141. strcpy(req->outgoing.buffer,"HTTP/1.1 200 OK\n" );
  142. req->outgoing.remaining=strlen(req->outgoing.buffer);
  143. envoiBuffer(req);
  144. }
  145. void notFoundRequest(Request *req)
  146. {
  147. strcpy(req->outgoing.buffer,"404 Not Found\n" );
  148. req->outgoing.remaining=strlen(req->outgoing.buffer);
  149. envoiBuffer(req);
  150. }
  151. void *manageRequest(void * data)
  152. {
  153. int r=0,ficD=0;
  154. char parametresCGI[0x20];
  155. Request * req=(Request *)data;
  156. pthread_detach(pthread_self());
  157. /* Récupération de la première ligne. */
  158. req->incoming.remaining=requestReadLine(req,req->incoming.buffer,BUFFER_SIZE);
  159. sscanf(req->incoming.buffer,"%s %s",req->requestMethod,req->requestUri);
  160. printf("Requete : %s %s\n",req->requestMethod,req->requestUri);
  161. /* Récupération du reste de l'entête de la requête HTTP. */
  162. do
  163. {
  164.  req->incoming.remaining=requestReadLine(req,req->incoming.buffer,BUFFER_SIZE);
  165.  printf("%s",req->incoming.buffer);
  166. } while(strcmp(req->incoming.buffer,"\n" )!=0 && strcmp(req->incoming.buffer,"\r\n" )!=0);
  167. /* Traitement de la requête. */
  168. if(strcmp(req->requestMethod,"GET" )!=0 && strcmp(req->requestMethod,"POST" )!=0) /* Si ce n'est pas une requête GET. */
  169. {
  170.  badRequest(req);
  171. }
  172. else if(strcmp(req->requestMethod,"GET" )==0)/* Si c'est une requête GET. */
  173. {
  174.  okRequest(req);
  175.  /* Fabrication du champ fileName. */
  176.  strcpy(req->fileName,"." );
  177.  strcat(req->fileName,req->requestUri);
  178.  /* Suppression des des éventuels paramètres CGI. */
  179.  suppressionParametresCGI(req,parametresCGI);
  180.  /* Récupération des informations. */
  181.  {
  182.   struct stat infos;
  183.   RESTART_SYSCALL(r,stat(req->fileName,&infos));
  184.   if(r==-1) { perror("stat" ); return (void *)0; }
  185.   /* S'il s'agit d'un dossier on ajoute index.html*/
  186.   if(S_ISDIR(infos.st_mode))
  187.   {
  188.    strcat(req->fileName,"/index.html" );
  189.   }
  190.   /* Sinon, on n'ajoute rien et on ouvre le fichier. */
  191.   RESTART_SYSCALL(ficD,open(req->fileName,O_RDONLY));
  192.   if(ficD==-1) /* Si l'ouverture pose problème. */
  193.   {
  194.    notFoundRequest(req);
  195.    return (void *)0;
  196.   }
  197.   /* Envoi de l'entête. */
  198.   {
  199.    char type[0x10];
  200.    contentType(req,type);
  201.    if(access(req->fileName,X_OK!=-1)) /* S'il ne s'agit pas d'un fichier exécutable : */
  202.    {
  203.     /* Envoi de la taille de l'entête. */
  204.     sprintf(req->outgoing.buffer,"Content-Length : %lu\n",infos.st_size);
  205.     envoiBuffer(req);
  206.     /* Envoi du type de l'entête */
  207.     sprintf(req->outgoing.buffer,"Content-Type : %s\n\n",type);
  208.     envoiBuffer(req);
  209.     /* Si l'ouverture fonctionne sans problème. */
  210.     do
  211.     {
  212.      req->outgoing.remaining=readFully(ficD,req->outgoing.buffer,BUFFER_SIZE);
  213.      req->outgoing.remaining=requestWriteFully(req,req->outgoing.buffer,req->outgoing.remaining); /* Impossibilité d'utiliser envoiBuffer car la taille du buffer est ici déterminée par le nombre d'octets lu par readFully. */
  214.     } while (req->outgoing.remaining != 0);
  215.     close(ficD); /* Fermeture du descripteur de fichier. */
  216.    }
  217.    else /* S'il s'agit d'un exécutable : */
  218.    {
  219.     execCGI(req);
  220.    }
  221.   }
  222.  }
  223. }
  224. else if(strcmp(req->requestMethod,"POST" )==0)/* Si c'est une requête POST. */
  225. {
  226.  printf("Filename : %s, Uri : %s\n",req->fileName,req->requestUri);
  227. }
  228. destroyRequest(req);
  229. return (void *)0;
  230. }
  231. /*--------------------------------------------------------------------------*/
  232. int main(void)
  233. {
  234. int httpSock=-1,r;
  235. struct sigaction act;
  236. pthread_t th;
  237. /* Ecoute sur le port 8080. */
  238. httpSock=listenTcp(8080);
  239. if(httpSock==-1)
  240. {
  241.  perror("listen http" );
  242.  exit(EXIT_FAILURE);
  243. }
  244. /* Ignorer une écriture impossible dans un pipe */
  245. memset(&act,0,sizeof(struct sigaction));
  246. act.sa_handler=SIG_IGN;
  247. RESTART_SYSCALL(r,sigaction(SIGPIPE,&act,(struct sigaction *)0));
  248. for(;;)
  249. {
  250.  unsigned long ipAddress;
  251.  int portNumber;
  252.  int fd=acceptTcp(httpSock,&ipAddress,&portNumber);
  253.  if(fd==-1)
  254.  {
  255.   perror("acceptTcp" );
  256.  }
  257.  else
  258.  {
  259.   Request * req=createRequest(fd,(SSL_CTX *)0);
  260.   r=pthread_create(&th,(pthread_attr_t *)0,&manageRequest,req);
  261.   if(r)
  262.   {
  263.    fprintf(stderr,"pthread_create: %s\n",strerror(errno));
  264.    destroyRequest(req);
  265.   }
  266.  }
  267. }
  268. return EXIT_SUCCESS; /* never reached */
  269. }
  270. /*--------------------------------------------------------------------------*/

Reply

Marsh Posté le 14-10-2007 à 16:40:29   

Reply

Marsh Posté le 15-10-2007 à 03:06:21    

Oué, il est hyper casse gueule ton code, je remettrais tout à plat, si j'étais toi, avec une gestion un peu maniaque des chaines de caractères. Le C n'est pas d'une très grande aide dans ce domaine, mais il est possible de faire des trucs potables avec un peu plus de rigueur.
 
Il y a des buffers overflow à gogo dans ton code, mais le plus probable se trouve sans doute à cette ligne :
 
char type[0x10];
contentType(req,type);
 
Un content-type possible est "application/octet-stream", ce qui fait largement plus que 16 octets. D'où écrasement de la valeur de retour et plantage quasi certain lorsque le thread quitte.

Reply

Marsh Posté le 15-10-2007 à 07:55:52    

Casse gueule mon code ? http://membres.lycos.fr/sebxoiii/Smileys/transpi.gif
 
C'est clair qu'il est pas formidable, mais je t'avouerais qu'on n'a jamais vraiment eu de cours de mise en forme de code. :/
 
Effectivement le type[0x10] c'est une grosse erreur, on l'avait mis de cette taille sans savoir la taille d'une description, et oublié de le modifier après.
 
Merci beaucoup pour ton aide, je verrais ce soir si ça passe. :)

Reply

Marsh Posté le 15-10-2007 à 08:31:50    

Sebxoii a écrit :

Casse gueule mon code ?
 
C'est clair qu'il est pas formidable, mais je t'avouerais qu'on n'a jamais vraiment eu de cours de mise en forme de code. :/


On s'en fout de la présentation (quoique...). Ce qu'on essaye de te faire comprendre, c'est qu'il est fragile du fait qu'il n'y a aucun contrôle sur la taille des chaines etc.
 


---------------
Des infos sur la programmation et le langage C: http://www.bien-programmer.fr Pas de Wi-Fi à la maison : http://www.cpl-france.org/
Reply

Marsh Posté le 15-10-2007 à 10:16:01    

Sebxoii a écrit :

Casse gueule mon code ? http://membres.lycos.fr/sebxoiii/Smileys/transpi.gif
 
C'est clair qu'il est pas formidable, mais je t'avouerais qu'on n'a jamais vraiment eu de cours de mise en forme de code. :/
 
Effectivement le type[0x10] c'est une grosse erreur, on l'avait mis de cette taille sans savoir la taille d'une description, et oublié de le modifier après.
 
Merci beaucoup pour ton aide, je verrais ce soir si ça passe. :)


Ne jamais utiliser strcpy ?
Faire attention avec l'utilisation de strncpy, utiliser une variante de strlcpy si on a.

Reply

Marsh Posté le 15-10-2007 à 16:03:40    

Emmanuel Delahaye a écrit :


On s'en fout de la présentation (quoique...). Ce qu'on essaye de te faire comprendre, c'est qu'il est fragile du fait qu'il n'y a aucun contrôle sur la taille des chaines etc.


Ah je pensais que la critique portait sur la répartition des fonctions/sous-fonctions que je fais un peu à l'occasion. Enfin merci pour les conseils. :)

Taz a écrit :


Ne jamais utiliser strcpy ?
Faire attention avec l'utilisation de strncpy, utiliser une variante de strlcpy si on a.


Je vais modifier ça à l'occasion.

 

edit : Merci tpierron, ça fonctionne nickel en modifiant la taille du buffer type. :)

 

Maintenant je vais essayer de rajouter quelques vérifications dès que j'écris dans un buffer. Je vais faire avec strncpy n'ayant pas strlcpy.

 

edit2 : Hmm en fait strncpy a l'air plutôt relou à utiliser, je viens de trouver la source de strlcpy, ça m'a l'air pas mal du tout. :)


Message édité par Sebxoii le 15-10-2007 à 16:21:07
Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed