CVE-2022-29967 (C, published 2022-04-29T23:15Z)
Description: value: static_compressed_inmemory_website_callback.c in Glewlwyd through 2.6.2 allows directory traversal.
Severity: MEDIUM (CVSS v3 base score: 7.5)
Weakness: CWE-22 \u2014 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
Repository: https://github.com/babelouest/glewlwyd
Files changed:
src/static_compressed_inmemory_website_callback.c
src/static_compressed_inmemory_website_callback.h
Commit message: Fix file access check for directory traversal, and fix call for callback_static_file_uncompressed if header not set


Vulnerable code:
 * Version 20220425
  char * file_requested, * file_path, * url_dup_save;
    f = fopen (file_path, "rb");
    if (f) {
      fseek (f, 0, SEEK_END);
      length = ftell (f);
      fseek (f, 0, SEEK_SET);

      content_type = u_map_get_case(&((struct _u_compressed_inmemory_website_config *)user_data)->mime_types, get_filename_ext(file_requested));
      if (content_type == NULL) {
        content_type = u_map_get(&((struct _u_compressed_inmemory_website_config *)user_data)->mime_types, "*");
        y_log_message(Y_LOG_LEVEL_WARNING, "Static File Server - Unknown mime type for extension %s", get_filename_ext(file_requested));
      }
      u_map_put(response->map_header, "Content-Type", content_type);
      u_map_copy_into(response->map_header, &((struct _u_compressed_inmemory_website_config *)user_data)->map_header);

      if (ulfius_set_stream_response(response, 200, callback_static_file_uncompressed_stream, callback_static_file_uncompressed_stream_free, length, CHUNK, f) != U_OK) {
        y_log_message(Y_LOG_LEVEL_ERROR, "Static File Server - Error ulfius_set_stream_response");
      }
    } else {
      if (((struct _u_compressed_inmemory_website_config *)user_data)->redirect_on_404 == NULL) {
        ret = U_CALLBACK_IGNORE;
        ulfius_add_header_to_response(response, "Location", ((struct _u_compressed_inmemory_website_config *)user_data)->redirect_on_404);
        response->status = 302;
    o_free(url_dup_save);
  char * file_requested, * file_path, * url_dup_save, * data_zip = NULL;

            
            
            if (!pthread_mutex_lock(&config->lock)) {
              f = fopen (file_path, "rb");
              if (f) {
                content_type = u_map_get_case(&config->mime_types, get_filename_ext(file_requested));
                if (content_type == NULL) {
                  content_type = u_map_get(&config->mime_types, "*");
                  y_log_message(Y_LOG_LEVEL_WARNING, "Static File Server - Unknown mime type for extension %s", get_filename_ext(file_requested));
                }
                if (!string_array_has_value((const char **)config->mime_types_compressed, content_type)) {
                  compress_mode = U_COMPRESS_NONE;
                }

                u_map_put(response->map_header, "Content-Type", content_type);
                u_map_copy_into(response->map_header, &config->map_header);
                
                fseek (f, 0, SEEK_END);
                offset = length = ftell (f);
                fseek (f, 0, SEEK_SET);

                if (length) {
                  if ((file_content_orig = file_content = o_malloc(length)) != NULL && (data_zip = o_malloc((2*length)+20)) != NULL) {
                    defstream.zalloc = u_zalloc;
                    defstream.zfree = u_zfree;
                    defstream.opaque = Z_NULL;
                    defstream.avail_in = (uInt)length;
                    defstream.next_in = (Bytef *)file_content;
                    while ((read_length = fread(file_content, sizeof(char), offset, f))) {
                      file_content += read_length;
                      offset -= read_length;
                    }

                    if (compress_mode == U_COMPRESS_GZIP) {
                      if (deflateInit2(&defstream,
                                       Z_DEFAULT_COMPRESSION,
                                       Z_DEFLATED,
                                       U_GZIP_WINDOW_BITS | U_GZIP_ENCODING,
                                       8,
                                       Z_DEFAULT_STRATEGY) != Z_OK) {
                        y_log_message(Y_LOG_LEVEL_ERROR, "callback_static_compressed_inmemory_website - Error deflateInit (gzip)");
                        ret = U_CALLBACK_ERROR;
                      }
                    } else {
                      if (deflateInit(&defstream, Z_BEST_COMPRESSION) != Z_OK) {
                        y_log_message(Y_LOG_LEVEL_ERROR, "callback_static_compressed_inmemory_website - Error deflateInit (deflate)");
                        ret = U_CALLBACK_ERROR;
                    }
                    if (ret == U_CALLBACK_CONTINUE) {
                      do {
                        if ((data_zip = o_realloc(data_zip, data_zip_len+_U_W_BLOCK_SIZE)) != NULL) {
                          defstream.avail_out = _U_W_BLOCK_SIZE;
                          defstream.next_out = ((Bytef *)data_zip)+data_zip_len;
                          switch ((res = deflate(&defstream, Z_FINISH))) {
                            case Z_OK:
                            case Z_STREAM_END:
                            case Z_BUF_ERROR:
                              break;
                            default:
                              y_log_message(Y_LOG_LEVEL_ERROR, "callback_static_compressed_inmemory_website - Error deflate %d", res);
                              ret = U_CALLBACK_ERROR;
                              break;
                          }
                          data_zip_len += _U_W_BLOCK_SIZE - defstream.avail_out;
                        } else {
                          y_log_message(Y_LOG_LEVEL_ERROR, "callback_static_compressed_inmemory_website - Error allocating resources for data_zip");
                      } while (U_CALLBACK_CONTINUE == ret && defstream.avail_out == 0);

                        if (compress_mode == U_COMPRESS_GZIP) {
                          if (config->allow_cache_compressed) {
                            u_map_put_binary(&config->gzip_files, file_requested, data_zip, 0, defstream.total_out);
                          ulfius_set_binary_body_response(response, 200, u_map_get(&config->gzip_files, file_requested), u_map_get_length(&config->gzip_files, file_requested));
                        } else {
                          if (config->allow_cache_compressed) {
                            u_map_put_binary(&config->deflate_files, file_requested, data_zip, 0, defstream.total_out);
                          ulfius_set_binary_body_response(response, 200, u_map_get(&config->deflate_files, file_requested), u_map_get_length(&config->deflate_files, file_requested));
                        u_map_put(response->map_header, U_CONTENT_HEADER, compress_mode==U_COMPRESS_GZIP?U_ACCEPT_GZIP:U_ACCEPT_DEFLATE);
                    deflateEnd(&defstream);
                    o_free(data_zip);
                    y_log_message(Y_LOG_LEVEL_ERROR, "callback_static_compressed_inmemory_website - Error allocating resource for file_content or data_zip");
                    ret = U_CALLBACK_ERROR;
                  o_free(file_content_orig);
                fclose(f);
                if (((struct _u_compressed_inmemory_website_config *)user_data)->redirect_on_404 == NULL) {
                  ret = U_CALLBACK_IGNORE;
                } else {
                  ulfius_add_header_to_response(response, "Location", ((struct _u_compressed_inmemory_website_config *)user_data)->redirect_on_404);
                  response->status = 302;
                }
              pthread_mutex_unlock(&config->lock);
              y_log_message(Y_LOG_LEVEL_ERROR, "callback_static_compressed_inmemory_website - Error pthread_lock_mutex");
              ret = U_CALLBACK_ERROR;
 * Version 20220425

Fixed code:
 * Version 20220428
#include <limits.h>
#include <stdlib.h>
  char * file_requested, * file_path, * url_dup_save, * real_path = NULL;
    real_path = realpath(file_path, NULL);
    if (0 == o_strncmp(((struct _u_compressed_inmemory_website_config *)user_data)->files_path, real_path, o_strlen(((struct _u_compressed_inmemory_website_config *)user_data)->files_path))) {
      f = fopen (file_path, "rb");
      if (f) {
        fseek (f, 0, SEEK_END);
        length = ftell (f);
        fseek (f, 0, SEEK_SET);

        content_type = u_map_get_case(&((struct _u_compressed_inmemory_website_config *)user_data)->mime_types, get_filename_ext(file_requested));
        if (content_type == NULL) {
          content_type = u_map_get(&((struct _u_compressed_inmemory_website_config *)user_data)->mime_types, "*");
          y_log_message(Y_LOG_LEVEL_WARNING, "Static File Server - Unknown mime type for extension %s", get_filename_ext(file_requested));
        }
        u_map_put(response->map_header, "Content-Type", content_type);
        u_map_copy_into(response->map_header, &((struct _u_compressed_inmemory_website_config *)user_data)->map_header);
        if (ulfius_set_stream_response(response, 200, callback_static_file_uncompressed_stream, callback_static_file_uncompressed_stream_free, length, CHUNK, f) != U_OK) {
          y_log_message(Y_LOG_LEVEL_ERROR, "Static File Server - Error ulfius_set_stream_response");
        }
        if (((struct _u_compressed_inmemory_website_config *)user_data)->redirect_on_404 == NULL) {
          ret = U_CALLBACK_IGNORE;
        } else {
          ulfius_add_header_to_response(response, "Location", ((struct _u_compressed_inmemory_website_config *)user_data)->redirect_on_404);
          response->status = 302;
        }
      o_free(url_dup_save);
    } else {
      response->status = 403;
    free(real_path); // realpath uses malloc

  char * file_requested, * file_path, * url_dup_save, * data_zip = NULL, * real_path = NULL;


            real_path = realpath(file_path, NULL);
            if (0 == o_strncmp(((struct _u_compressed_inmemory_website_config *)user_data)->files_path, real_path, o_strlen(((struct _u_compressed_inmemory_website_config *)user_data)->files_path))) {
              if (!pthread_mutex_lock(&config->lock)) {
                f = fopen (file_path, "rb");
                if (f) {
                  content_type = u_map_get_case(&config->mime_types, get_filename_ext(file_requested));
                  if (content_type == NULL) {
                    content_type = u_map_get(&config->mime_types, "*");
                    y_log_message(Y_LOG_LEVEL_WARNING, "Static File Server - Unknown mime type for extension %s", get_filename_ext(file_requested));
                  }
                  if (!string_array_has_value((const char **)config->mime_types_compressed, content_type)) {
                    compress_mode = U_COMPRESS_NONE;
                  }
                  u_map_put(response->map_header, "Content-Type", content_type);
                  u_map_copy_into(response->map_header, &config->map_header);

                  fseek (f, 0, SEEK_END);
                  offset = length = ftell (f);
                  fseek (f, 0, SEEK_SET);

                  if (length) {
                    if ((file_content_orig = file_content = o_malloc(length)) != NULL && (data_zip = o_malloc((2*length)+20)) != NULL) {
                      defstream.zalloc = u_zalloc;
                      defstream.zfree = u_zfree;
                      defstream.opaque = Z_NULL;
                      defstream.avail_in = (uInt)length;
                      defstream.next_in = (Bytef *)file_content;
                      while ((read_length = fread(file_content, sizeof(char), offset, f))) {
                        file_content += read_length;
                        offset -= read_length;

                      if (compress_mode == U_COMPRESS_GZIP) {
                        if (deflateInit2(&defstream,
                                         Z_DEFAULT_COMPRESSION,
                                         Z_DEFLATED,
                                         U_GZIP_WINDOW_BITS | U_GZIP_ENCODING,
                                         8,
                                         Z_DEFAULT_STRATEGY) != Z_OK) {
                          y_log_message(Y_LOG_LEVEL_ERROR, "callback_static_compressed_inmemory_website - Error deflateInit (gzip)");
                      } else {
                        if (deflateInit(&defstream, Z_BEST_COMPRESSION) != Z_OK) {
                          y_log_message(Y_LOG_LEVEL_ERROR, "callback_static_compressed_inmemory_website - Error deflateInit (deflate)");
                          ret = U_CALLBACK_ERROR;
                        }
                      }
                        do {
                          if ((data_zip = o_realloc(data_zip, data_zip_len+_U_W_BLOCK_SIZE)) != NULL) {
                            defstream.avail_out = _U_W_BLOCK_SIZE;
                            defstream.next_out = ((Bytef *)data_zip)+data_zip_len;
                            switch ((res = deflate(&defstream, Z_FINISH))) {
                              case Z_OK:
                              case Z_STREAM_END:
                              case Z_BUF_ERROR:
                                break;
                              default:
                                y_log_message(Y_LOG_LEVEL_ERROR, "callback_static_compressed_inmemory_website - Error deflate %d", res);
                                ret = U_CALLBACK_ERROR;
                                break;
                            }
                            data_zip_len += _U_W_BLOCK_SIZE - defstream.avail_out;
                          } else {
                            y_log_message(Y_LOG_LEVEL_ERROR, "callback_static_compressed_inmemory_website - Error allocating resources for data_zip");
                            ret = U_CALLBACK_ERROR;
                        } while (U_CALLBACK_CONTINUE == ret && defstream.avail_out == 0);

                        if (ret == U_CALLBACK_CONTINUE) {
                          if (compress_mode == U_COMPRESS_GZIP) {
                            if (config->allow_cache_compressed) {
                              u_map_put_binary(&config->gzip_files, file_requested, data_zip, 0, defstream.total_out);
                            }
                            ulfius_set_binary_body_response(response, 200, u_map_get(&config->gzip_files, file_requested), u_map_get_length(&config->gzip_files, file_requested));
                          } else {
                            if (config->allow_cache_compressed) {
                              u_map_put_binary(&config->deflate_files, file_requested, data_zip, 0, defstream.total_out);
                            }
                            ulfius_set_binary_body_response(response, 200, u_map_get(&config->deflate_files, file_requested), u_map_get_length(&config->deflate_files, file_requested));
                          u_map_put(response->map_header, U_CONTENT_HEADER, compress_mode==U_COMPRESS_GZIP?U_ACCEPT_GZIP:U_ACCEPT_DEFLATE);
                      deflateEnd(&defstream);
                      o_free(data_zip);
                    } else {
                      y_log_message(Y_LOG_LEVEL_ERROR, "callback_static_compressed_inmemory_website - Error allocating resource for file_content or data_zip");
                      ret = U_CALLBACK_ERROR;
                    o_free(file_content_orig);
                  }
                  fclose(f);
                } else {
                  if (((struct _u_compressed_inmemory_website_config *)user_data)->redirect_on_404 == NULL) {
                    ret = U_CALLBACK_IGNORE;
                    ulfius_add_header_to_response(response, "Location", ((struct _u_compressed_inmemory_website_config *)user_data)->redirect_on_404);
                    response->status = 302;
                pthread_mutex_unlock(&config->lock);
                y_log_message(Y_LOG_LEVEL_ERROR, "callback_static_compressed_inmemory_website - Error pthread_lock_mutex");
                ret = U_CALLBACK_ERROR;
              response->status = 403;
            free(real_path); // realpath uses malloc
      } else {
        ret = callback_static_file_uncompressed(request, response, user_data);
    } else {
      ret = callback_static_file_uncompressed(request, response, user_data);
 * Version 20220428