lib/dload: prevent large file attacks

This means creating a new struct which can pass more descriptive data
from the back end sync functions to the downloader. In particular, we're
interested in the download size read from the sync DB. When the remote
server reports a size larger than this (via a content-length header),
abort the transfer.

In cases where the size is unknown, we set a hard upper limit of:

* 25MiB for a sync DB
* 16KiB for a signature

For reference, 25MiB is more than twice the size of all of the current
binary repos (with files) combined, and 16KiB is a truly gargantuan
signature.

Signed-off-by: Dave Reisner <dreisner@archlinux.org>
This commit is contained in:
Dave Reisner 2011-06-24 00:18:01 -04:00 committed by Dave Reisner
parent 6c9b82e72a
commit 6dc71926f9
4 changed files with 87 additions and 40 deletions

View file

@ -172,16 +172,21 @@ int SYMEXPORT alpm_db_update(int force, alpm_db_t *db)
for(i = db->servers; i; i = i->next) { for(i = db->servers; i; i = i->next) {
const char *server = i->data; const char *server = i->data;
char *fileurl; struct dload_payload *payload;
size_t len; size_t len;
int sig_ret = 0; int sig_ret = 0;
CALLOC(payload, 1, sizeof(*payload), RET_ERR(handle, ALPM_ERR_MEMORY, -1));
/* set hard upper limit of 25MiB */
payload->max_size = 25 * 1024 * 1024;
/* print server + filename into a buffer (leave space for .sig) */ /* print server + filename into a buffer (leave space for .sig) */
len = strlen(server) + strlen(db->treename) + 9; len = strlen(server) + strlen(db->treename) + 9;
CALLOC(fileurl, len, sizeof(char), RET_ERR(handle, ALPM_ERR_MEMORY, -1)); CALLOC(payload->fileurl, len, sizeof(char), RET_ERR(handle, ALPM_ERR_MEMORY, -1));
snprintf(fileurl, len, "%s/%s.db", server, db->treename); snprintf(payload->fileurl, len, "%s/%s.db", server, db->treename);
ret = _alpm_download(handle, fileurl, syncpath, NULL, force, 0, 0); ret = _alpm_download(handle, payload, syncpath, NULL, force, 0, 0);
if(ret == 0 && (level & ALPM_SIG_DATABASE)) { if(ret == 0 && (level & ALPM_SIG_DATABASE)) {
/* an existing sig file is no good at this point */ /* an existing sig file is no good at this point */
@ -195,14 +200,17 @@ int SYMEXPORT alpm_db_update(int force, alpm_db_t *db)
int errors_ok = (level & ALPM_SIG_DATABASE_OPTIONAL); int errors_ok = (level & ALPM_SIG_DATABASE_OPTIONAL);
/* if we downloaded a DB, we want the .sig from the same server */ /* if we downloaded a DB, we want the .sig from the same server */
snprintf(fileurl, len, "%s/%s.db.sig", server, db->treename); snprintf(payload->fileurl, len, "%s/%s.db.sig", server, db->treename);
sig_ret = _alpm_download(handle, fileurl, syncpath, NULL, 1, 0, errors_ok); /* set hard upper limit of 16KiB */
payload->max_size = 16 * 1024;
sig_ret = _alpm_download(handle, payload, syncpath, NULL, 1, 0, errors_ok);
/* errors_ok suppresses error messages, but not the return code */ /* errors_ok suppresses error messages, but not the return code */
sig_ret = errors_ok ? 0 : sig_ret; sig_ret = errors_ok ? 0 : sig_ret;
} }
FREE(fileurl); _alpm_dload_payload_free(payload);
if(ret != -1 && sig_ret != -1) { if(ret != -1 && sig_ret != -1) {
break; break;
} }

View file

@ -177,7 +177,7 @@ static size_t parse_headers(void *ptr, size_t size, size_t nmemb, void *user)
return realsize; return realsize;
} }
static int curl_download_internal(alpm_handle_t *handle, const char *url, static int curl_download_internal(alpm_handle_t *handle, struct dload_payload *payload,
const char *localpath, char **final_file, int force, int allow_resume, const char *localpath, char **final_file, int force, int allow_resume,
int errors_ok) int errors_ok)
{ {
@ -197,10 +197,10 @@ static int curl_download_internal(alpm_handle_t *handle, const char *url,
dlfile.handle = handle; dlfile.handle = handle;
dlfile.initial_size = 0.0; dlfile.initial_size = 0.0;
dlfile.filename = get_filename(url); dlfile.filename = payload->filename ? payload->filename : get_filename(payload->fileurl);
dlfile.cd_filename = NULL; dlfile.cd_filename = NULL;
if(!dlfile.filename || curl_gethost(url, hostname) != 0) { if(!dlfile.filename || curl_gethost(payload->fileurl, hostname) != 0) {
_alpm_log(handle, ALPM_LOG_ERROR, _("url '%s' is invalid\n"), url); _alpm_log(handle, ALPM_LOG_ERROR, _("url '%s' is invalid\n"), payload->fileurl);
RET_ERR(handle, ALPM_ERR_SERVER_BAD_URL, -1); RET_ERR(handle, ALPM_ERR_SERVER_BAD_URL, -1);
} }
@ -238,7 +238,7 @@ static int curl_download_internal(alpm_handle_t *handle, const char *url,
/* the curl_easy handle is initialized with the alpm handle, so we only need /* the curl_easy handle is initialized with the alpm handle, so we only need
* to reset the curl handle set parameters for each time it's used. */ * to reset the curl handle set parameters for each time it's used. */
curl_easy_reset(handle->curl); curl_easy_reset(handle->curl);
curl_easy_setopt(handle->curl, CURLOPT_URL, url); curl_easy_setopt(handle->curl, CURLOPT_URL, payload->fileurl);
curl_easy_setopt(handle->curl, CURLOPT_FAILONERROR, 1L); curl_easy_setopt(handle->curl, CURLOPT_FAILONERROR, 1L);
curl_easy_setopt(handle->curl, CURLOPT_ERRORBUFFER, error_buffer); curl_easy_setopt(handle->curl, CURLOPT_ERRORBUFFER, error_buffer);
curl_easy_setopt(handle->curl, CURLOPT_CONNECTTIMEOUT, 10L); curl_easy_setopt(handle->curl, CURLOPT_CONNECTTIMEOUT, 10L);
@ -252,6 +252,10 @@ static int curl_download_internal(alpm_handle_t *handle, const char *url,
curl_easy_setopt(handle->curl, CURLOPT_HEADERFUNCTION, parse_headers); curl_easy_setopt(handle->curl, CURLOPT_HEADERFUNCTION, parse_headers);
curl_easy_setopt(handle->curl, CURLOPT_WRITEHEADER, &dlfile); curl_easy_setopt(handle->curl, CURLOPT_WRITEHEADER, &dlfile);
if(payload->max_size) {
curl_easy_setopt(handle->curl, CURLOPT_MAXFILESIZE, payload->max_size);
}
useragent = getenv("HTTP_USER_AGENT"); useragent = getenv("HTTP_USER_AGENT");
if(useragent != NULL) { if(useragent != NULL) {
curl_easy_setopt(handle->curl, CURLOPT_USERAGENT, useragent); curl_easy_setopt(handle->curl, CURLOPT_USERAGENT, useragent);
@ -407,18 +411,19 @@ cleanup:
* @param errors_ok do not log errors (but still return them) * @param errors_ok do not log errors (but still return them)
* @return 0 on success, -1 on error (pm_errno is set accordingly if errors_ok == 0) * @return 0 on success, -1 on error (pm_errno is set accordingly if errors_ok == 0)
*/ */
int _alpm_download(alpm_handle_t *handle, const char *url, const char *localpath, int _alpm_download(alpm_handle_t *handle, struct dload_payload *payload,
char **final_file, int force, int allow_resume, int errors_ok) const char *localpath, char **final_file, int force, int allow_resume,
int errors_ok)
{ {
if(handle->fetchcb == NULL) { if(handle->fetchcb == NULL) {
#ifdef HAVE_LIBCURL #ifdef HAVE_LIBCURL
return curl_download_internal(handle, url, localpath, final_file, force, return curl_download_internal(handle, payload, localpath, final_file, force,
allow_resume, errors_ok); allow_resume, errors_ok);
#else #else
RET_ERR(handle, ALPM_ERR_EXTERNAL_DOWNLOAD, -1); RET_ERR(handle, ALPM_ERR_EXTERNAL_DOWNLOAD, -1);
#endif #endif
} else { } else {
int ret = handle->fetchcb(url, localpath, force); int ret = handle->fetchcb(payload->fileurl, localpath, force);
if(ret == -1 && !errors_ok) { if(ret == -1 && !errors_ok) {
RET_ERR(handle, ALPM_ERR_EXTERNAL_DOWNLOAD, -1); RET_ERR(handle, ALPM_ERR_EXTERNAL_DOWNLOAD, -1);
} }
@ -432,6 +437,7 @@ char SYMEXPORT *alpm_fetch_pkgurl(alpm_handle_t *handle, const char *url)
char *filepath; char *filepath;
const char *cachedir; const char *cachedir;
char *final_file = NULL; char *final_file = NULL;
struct dload_payload *payload;
int ret; int ret;
CHECK_HANDLE(handle, return NULL); CHECK_HANDLE(handle, return NULL);
@ -439,8 +445,11 @@ char SYMEXPORT *alpm_fetch_pkgurl(alpm_handle_t *handle, const char *url)
/* find a valid cache dir to download to */ /* find a valid cache dir to download to */
cachedir = _alpm_filecache_setup(handle); cachedir = _alpm_filecache_setup(handle);
CALLOC(payload, 1, sizeof(*payload), RET_ERR(handle, ALPM_ERR_MEMORY, NULL));
payload->fileurl = strdup(url);
/* download the file */ /* download the file */
ret = _alpm_download(handle, url, cachedir, &final_file, 0, 1, 0); ret = _alpm_download(handle, payload, cachedir, &final_file, 0, 1, 0);
if(ret == -1) { if(ret == -1) {
_alpm_log(handle, ALPM_LOG_WARNING, _("failed to download %s\n"), url); _alpm_log(handle, ALPM_LOG_WARNING, _("failed to download %s\n"), url);
return NULL; return NULL;
@ -449,32 +458,44 @@ char SYMEXPORT *alpm_fetch_pkgurl(alpm_handle_t *handle, const char *url)
/* attempt to download the signature */ /* attempt to download the signature */
if(ret == 0 && (handle->siglevel & ALPM_SIG_PACKAGE)) { if(ret == 0 && (handle->siglevel & ALPM_SIG_PACKAGE)) {
char *sig_url;
char *sig_final_file = NULL; char *sig_final_file = NULL;
size_t len; size_t len;
int errors_ok = (handle->siglevel & ALPM_SIG_PACKAGE_OPTIONAL); int errors_ok = (handle->siglevel & ALPM_SIG_PACKAGE_OPTIONAL);
struct dload_payload *sig_payload;
CALLOC(sig_payload, 1, sizeof(*sig_payload), RET_ERR(handle, ALPM_ERR_MEMORY, NULL));
len = strlen(url) + 5; len = strlen(url) + 5;
CALLOC(sig_url, len, sizeof(char), RET_ERR(handle, ALPM_ERR_MEMORY, NULL)); CALLOC(sig_payload->fileurl, len, sizeof(char), RET_ERR(handle, ALPM_ERR_MEMORY, NULL));
snprintf(sig_url, len, "%s.sig", url); snprintf(sig_payload->fileurl, len, "%s.sig", url);
sig_payload->max_size = 1024 * 16;
ret = _alpm_download(handle, sig_url, cachedir, &sig_final_file, 1, 0, errors_ok); ret = _alpm_download(handle, sig_payload, cachedir, &sig_final_file, 1, 0, errors_ok);
if(ret == -1 && !errors_ok) { if(ret == -1 && !errors_ok) {
_alpm_log(handle, ALPM_LOG_WARNING, _("failed to download %s\n"), sig_url); _alpm_log(handle, ALPM_LOG_WARNING, _("failed to download %s\n"), sig_payload->fileurl);
/* Warn now, but don't return NULL. We will fail later during package /* Warn now, but don't return NULL. We will fail later during package
* load time. */ * load time. */
} else if(ret == 0) { } else if(ret == 0) {
_alpm_log(handle, ALPM_LOG_DEBUG, "successfully downloaded %s\n", sig_url); _alpm_log(handle, ALPM_LOG_DEBUG, "successfully downloaded %s\n", sig_payload->fileurl);
} }
FREE(sig_url);
FREE(sig_final_file); FREE(sig_final_file);
_alpm_dload_payload_free(sig_payload);
} }
/* we should be able to find the file the second time around */ /* we should be able to find the file the second time around */
filepath = _alpm_filecache_find(handle, final_file); filepath = _alpm_filecache_find(handle, final_file);
FREE(final_file); FREE(final_file);
_alpm_dload_payload_free(payload);
return filepath; return filepath;
} }
void _alpm_dload_payload_free(struct dload_payload *payload) {
struct dload_payload *load = (struct dload_payload *)payload;
ASSERT(load, return);
FREE(load->fileurl);
FREE(load);
}
/* vim: set ts=2 sw=2 noet: */ /* vim: set ts=2 sw=2 noet: */

View file

@ -33,8 +33,17 @@ struct fileinfo {
double initial_size; double initial_size;
}; };
int _alpm_download(alpm_handle_t *handle, const char *url, const char *localpath, struct dload_payload {
char **final_file, int force, int allow_resume, int errors_ok); char *filename;
char *fileurl;
long max_size;
};
void _alpm_dload_payload_free(struct dload_payload *payload);
int _alpm_download(alpm_handle_t *handle, struct dload_payload *payload,
const char *localpath, char **final_file, int force, int allow_resume,
int errors_ok);
#endif /* _ALPM_DLOAD_H */ #endif /* _ALPM_DLOAD_H */

View file

@ -765,10 +765,6 @@ static int download_files(alpm_handle_t *handle, alpm_list_t **deltas)
alpm_pkg_t *spkg = j->data; alpm_pkg_t *spkg = j->data;
if(spkg->origin != PKG_FROM_FILE && current == spkg->origin_data.db) { if(spkg->origin != PKG_FROM_FILE && current == spkg->origin_data.db) {
const char *fname = NULL;
fname = alpm_pkg_get_filename(spkg);
ASSERT(fname != NULL, RET_ERR(handle, ALPM_ERR_PKG_INVALID_NAME, -1));
alpm_list_t *delta_path = spkg->delta_path; alpm_list_t *delta_path = spkg->delta_path;
if(delta_path) { if(delta_path) {
/* using deltas */ /* using deltas */
@ -776,14 +772,27 @@ static int download_files(alpm_handle_t *handle, alpm_list_t **deltas)
for(dlts = delta_path; dlts; dlts = dlts->next) { for(dlts = delta_path; dlts; dlts = dlts->next) {
alpm_delta_t *delta = dlts->data; alpm_delta_t *delta = dlts->data;
if(delta->download_size != 0) { if(delta->download_size != 0) {
files = alpm_list_add(files, strdup(delta->delta)); struct dload_payload *dpayload;
CALLOC(dpayload, 1, sizeof(*dpayload), RET_ERR(handle, ALPM_ERR_MEMORY, -1));
STRDUP(dpayload->filename, delta->delta, RET_ERR(handle, ALPM_ERR_MEMORY, -1));
dpayload->max_size = delta->download_size;
files = alpm_list_add(files, dpayload);
} }
/* keep a list of all the delta files for md5sums */ /* keep a list of all the delta files for md5sums */
*deltas = alpm_list_add(*deltas, delta); *deltas = alpm_list_add(*deltas, delta);
} }
} else if(spkg->download_size != 0) { } else if(spkg->download_size != 0) {
files = alpm_list_add(files, strdup(fname)); struct dload_payload *payload;
ASSERT(spkg->filename != NULL, RET_ERR(handle, ALPM_ERR_PKG_INVALID_NAME, -1));
CALLOC(payload, 1, sizeof(*payload), RET_ERR(handle, ALPM_ERR_MEMORY, -1));
STRDUP(payload->filename, spkg->filename, RET_ERR(handle, ALPM_ERR_MEMORY, -1));
payload->max_size = alpm_pkg_get_size(spkg);
files = alpm_list_add(files, payload);
} }
} }
@ -792,21 +801,19 @@ static int download_files(alpm_handle_t *handle, alpm_list_t **deltas)
if(files) { if(files) {
EVENT(handle->trans, ALPM_TRANS_EVT_RETRIEVE_START, current->treename, NULL); EVENT(handle->trans, ALPM_TRANS_EVT_RETRIEVE_START, current->treename, NULL);
for(j = files; j; j = j->next) { for(j = files; j; j = j->next) {
const char *filename = j->data; struct dload_payload *payload = j->data;
alpm_list_t *server; alpm_list_t *server;
int ret = -1; int ret = -1;
for(server = current->servers; server; server = server->next) { for(server = current->servers; server; server = server->next) {
const char *server_url = server->data; const char *server_url = server->data;
char *fileurl;
size_t len; size_t len;
/* print server + filename into a buffer */ /* print server + filename into a buffer */
len = strlen(server_url) + strlen(filename) + 2; len = strlen(server_url) + strlen(payload->filename) + 2;
CALLOC(fileurl, len, sizeof(char), RET_ERR(handle, ALPM_ERR_MEMORY, -1)); CALLOC(payload->fileurl, len, sizeof(char), RET_ERR(handle, ALPM_ERR_MEMORY, -1));
snprintf(fileurl, len, "%s/%s", server_url, filename); snprintf(payload->fileurl, len, "%s/%s", server_url, payload->filename);
ret = _alpm_download(handle, fileurl, cachedir, NULL, 0, 1, 0); ret = _alpm_download(handle, payload, cachedir, NULL, 0, 1, 0);
FREE(fileurl);
if(ret != -1) { if(ret != -1) {
break; break;
} }
@ -816,7 +823,9 @@ static int download_files(alpm_handle_t *handle, alpm_list_t **deltas)
} }
} }
FREELIST(files); alpm_list_free_inner(files, (alpm_list_fn_free)_alpm_dload_payload_free);
alpm_list_free(files);
files = NULL;
if(errors) { if(errors) {
_alpm_log(handle, ALPM_LOG_WARNING, _("failed to retrieve some files from %s\n"), _alpm_log(handle, ALPM_LOG_WARNING, _("failed to retrieve some files from %s\n"),
current->treename); current->treename);