1
0
mirror of https://github.com/moparisthebest/wget synced 2024-07-03 16:38:41 -04:00

Fix potential race condition

* src/hsts.c (hsts_read_database): get an open file handle
   instead of a file name.
   (hsts_store_dump): get an open file handle
   instead of a file name.
   (hsts_store_open): open the file and pass the open file handle.
   (hsts_store_save): lock the file before the read-merge-dump
   process.

 Reported-by: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
This commit is contained in:
Ander Juaristi 2015-10-05 23:03:45 +02:00 committed by Tim Rühsen
parent 077e897819
commit f5a63e3100

View File

@ -39,13 +39,16 @@ as that of the covered work. */
#include "c-ctype.h" #include "c-ctype.h"
#ifdef TESTING #ifdef TESTING
#include "test.h" #include "test.h"
#include <unistd.h> /* for unlink(), used only in tests */
#endif #endif
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h> #include <stdlib.h>
#include <time.h> #include <time.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <string.h> #include <string.h>
#include <stdio.h>
#include <sys/file.h>
struct hsts_store { struct hsts_store {
struct hash_table *table; struct hash_table *table;
@ -265,9 +268,8 @@ hsts_store_merge (hsts_store_t store,
} }
static bool static bool
hsts_read_database (hsts_store_t store, const char *file, bool merge_with_existing_entries) hsts_read_database (hsts_store_t store, FILE *fp, bool merge_with_existing_entries)
{ {
FILE *fp = NULL;
char *line = NULL, *p; char *line = NULL, *p;
size_t len = 0; size_t len = 0;
int items_read; int items_read;
@ -281,9 +283,6 @@ hsts_read_database (hsts_store_t store, const char *file, bool merge_with_existi
func = (merge_with_existing_entries ? hsts_store_merge : hsts_new_entry); func = (merge_with_existing_entries ? hsts_store_merge : hsts_new_entry);
fp = fopen (file, "r");
if (fp)
{
while (getline (&line, &len, fp) > 0) while (getline (&line, &len, fp) > 0)
{ {
for (p = line; c_isspace (*p); p++) for (p = line; c_isspace (*p); p++)
@ -304,23 +303,16 @@ hsts_read_database (hsts_store_t store, const char *file, bool merge_with_existi
} }
xfree (line); xfree (line);
fclose (fp);
result = true; result = true;
}
return result; return result;
} }
static void static void
hsts_store_dump (hsts_store_t store, const char *filename) hsts_store_dump (hsts_store_t store, FILE *fp)
{ {
FILE *fp = NULL;
hash_table_iterator it; hash_table_iterator it;
fp = fopen (filename, "w");
if (fp)
{
/* Print preliminary comments. We don't care if any of these fail. */ /* Print preliminary comments. We don't care if any of these fail. */
fputs ("# HSTS 1.0 Known Hosts database for GNU Wget.\n", fp); fputs ("# HSTS 1.0 Known Hosts database for GNU Wget.\n", fp);
fputs ("# Edit at your own risk.\n", fp); fputs ("# Edit at your own risk.\n", fp);
@ -340,9 +332,6 @@ hsts_store_dump (hsts_store_t store, const char *filename)
break; break;
} }
} }
fclose (fp);
}
} }
/* HSTS API */ /* HSTS API */
@ -474,6 +463,7 @@ hsts_store_open (const char *filename)
{ {
hsts_store_t store = NULL; hsts_store_t store = NULL;
struct stat st; struct stat st;
FILE *fp = NULL;
store = xnew0 (struct hsts_store); store = xnew0 (struct hsts_store);
store->table = hash_table_new (0, hsts_hash_func, hsts_cmp_func); store->table = hash_table_new (0, hsts_hash_func, hsts_cmp_func);
@ -484,13 +474,15 @@ hsts_store_open (const char *filename)
if (stat (filename, &st) == 0) if (stat (filename, &st) == 0)
store->last_mtime = st.st_mtime; store->last_mtime = st.st_mtime;
if (!hsts_read_database (store, filename, false)) fp = fopen (filename, "r");
if (!fp || !hsts_read_database (store, fp, false))
{ {
/* abort! */ /* abort! */
hsts_store_close (store); hsts_store_close (store);
xfree (store); xfree (store);
store = NULL;
} }
if (fp)
fclose (fp);
} }
return store; return store;
@ -500,18 +492,36 @@ void
hsts_store_save (hsts_store_t store, const char *filename) hsts_store_save (hsts_store_t store, const char *filename)
{ {
struct stat st; struct stat st;
FILE *fp = NULL;
int fd = 0;
if (filename && hash_table_count (store->table) > 0) if (filename && hash_table_count (store->table) > 0)
{ {
fp = fopen (filename, "a+");
if (fp)
{
/* Lock the file to avoid potential race conditions */
fd = fileno (fp);
flock (fd, LOCK_EX);
/* If the file has changed, merge the changes with our in-memory data /* If the file has changed, merge the changes with our in-memory data
before dumping them to the file. before dumping them to the file.
Otherwise we could potentially overwrite the data stored by other Wget processes. Otherwise we could potentially overwrite the data stored by other Wget processes.
*/ */
if (store->last_mtime && stat (filename, &st) == 0 && st.st_mtime > store->last_mtime) if (store->last_mtime && stat (filename, &st) == 0 && st.st_mtime > store->last_mtime)
hsts_read_database (store, filename, true); hsts_read_database (store, fp, true);
/* We've merged the latest changes so we can now truncate the file
and dump everything. */
fseek (fp, 0, SEEK_SET);
ftruncate (fd, 0);
/* now dump to the file */ /* now dump to the file */
hsts_store_dump (store, filename); hsts_store_dump (store, fp);
/* fclose is expected to unlock the file for us */
fclose (fp);
}
} }
} }