Skip to content
This repository has been archived by the owner on Oct 20, 2024. It is now read-only.

Commit

Permalink
Fix file access check for directory traversal, and fix call for callb…
Browse files Browse the repository at this point in the history
…ack_static_file_uncompressed if header not set
  • Loading branch information
babelouest committed Apr 29, 2022
1 parent 5238266 commit e3f7245
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 120 deletions.
254 changes: 135 additions & 119 deletions src/static_compressed_inmemory_website_callback.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*
* Copyright 2020-2022 Nicolas Mora <mail@babelouest.org>
*
* Version 20220425
* Version 20220428
*
* The MIT License (MIT)
*
Expand Down Expand Up @@ -89,6 +89,8 @@
*/
#include <pthread.h>
#include <zlib.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <ulfius.h>

Expand Down Expand Up @@ -158,7 +160,7 @@ static void callback_static_file_uncompressed_stream_free(void * cls) {
static int callback_static_file_uncompressed (const struct _u_request * request, struct _u_response * response, void * user_data) {
size_t length;
FILE * f;
char * file_requested, * file_path, * url_dup_save;
char * file_requested, * file_path, * url_dup_save, * real_path = NULL;
const char * content_type;
int ret = U_CALLBACK_CONTINUE;

Expand All @@ -185,34 +187,40 @@ static int callback_static_file_uncompressed (const struct _u_request * request,
}

file_path = msprintf("%s/%s", ((struct _u_compressed_inmemory_website_config *)user_data)->files_path, file_requested);
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);

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;
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 {
ulfius_add_header_to_response(response, "Location", ((struct _u_compressed_inmemory_website_config *)user_data)->redirect_on_404);
response->status = 302;
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;
}
o_free(file_path);
o_free(url_dup_save);
free(real_path); // realpath uses malloc

} else {
y_log_message(Y_LOG_LEVEL_ERROR, "Static File Server - Error, user_data is NULL or inconsistent");
ret = U_CALLBACK_ERROR;
Expand Down Expand Up @@ -290,7 +298,7 @@ int callback_static_compressed_inmemory_website (const struct _u_request * reque
unsigned char * file_content, * file_content_orig = NULL;
size_t length, read_length, offset, data_zip_len = 0;
FILE * f;
char * file_requested, * file_path, * url_dup_save, * data_zip = NULL;
char * file_requested, * file_path, * url_dup_save, * data_zip = NULL, * real_path = NULL;
const char * content_type;

/*
Expand Down Expand Up @@ -328,12 +336,11 @@ int callback_static_compressed_inmemory_website (const struct _u_request * reque
compress_mode = U_COMPRESS_DEFL;
}


if (compress_mode != U_COMPRESS_NONE) {
if (compress_mode == U_COMPRESS_GZIP && config->allow_cache_compressed && u_map_has_key(&config->gzip_files, file_requested)) {
ulfius_set_binary_body_response(response, 200, u_map_get(&config->gzip_files, file_requested), u_map_get_length(&config->gzip_files, file_requested));
u_map_put(response->map_header, U_CONTENT_HEADER, U_ACCEPT_GZIP);

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, "*");
Expand All @@ -343,7 +350,7 @@ int callback_static_compressed_inmemory_website (const struct _u_request * reque
} else if (compress_mode == U_COMPRESS_DEFL && config->allow_cache_compressed && u_map_has_key(&config->deflate_files, file_requested)) {
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, U_ACCEPT_DEFLATE);

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, "*");
Expand All @@ -352,120 +359,129 @@ int callback_static_compressed_inmemory_website (const struct _u_request * reque
u_map_copy_into(response->map_header, &config->map_header);
} else {
file_path = msprintf("%s/%s", ((struct _u_compressed_inmemory_website_config *)user_data)->files_path, file_requested);
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;
}

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;
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 (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");

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;
}
} while (U_CALLBACK_CONTINUE == ret && defstream.avail_out == 0);

} 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) {
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);
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;
}
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);
} 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));
}
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);
}
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;
}
deflateEnd(&defstream);
o_free(data_zip);
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;
} 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;
ulfius_add_header_to_response(response, "Location", ((struct _u_compressed_inmemory_website_config *)user_data)->redirect_on_404);
response->status = 302;
}
o_free(file_content_orig);
}
fclose(f);
pthread_mutex_unlock(&config->lock);
} else {
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;
}
y_log_message(Y_LOG_LEVEL_ERROR, "callback_static_compressed_inmemory_website - Error pthread_lock_mutex");
ret = U_CALLBACK_ERROR;
}
pthread_mutex_unlock(&config->lock);
} else {
y_log_message(Y_LOG_LEVEL_ERROR, "callback_static_compressed_inmemory_website - Error pthread_lock_mutex");
ret = U_CALLBACK_ERROR;
response->status = 403;
}
o_free(file_path);
free(real_path); // realpath uses malloc
}
} else {
ret = callback_static_file_uncompressed(request, response, user_data);
}
free_string_array(accept_list);
} else {
ret = callback_static_file_uncompressed(request, response, user_data);
}
} else {
ret = callback_static_file_uncompressed(request, response, user_data);
}
o_free(url_dup_save);
}
Expand Down
2 changes: 1 addition & 1 deletion src/static_compressed_inmemory_website_callback.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*
* Copyright 2020-2022 Nicolas Mora <mail@babelouest.org>
*
* Version 20220425
* Version 20220428
*
* The MIT License (MIT)
*
Expand Down

1 comment on commit e3f7245

@carnil
Copy link

@carnil carnil commented on e3f7245 Apr 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CVE-2022-29967 references this commit.

Please sign in to comment.