src/utils_rrdcreate.c: Lock files to be created.
authorFlorian Forster <octo@collectd.org>
Sun, 24 Feb 2013 09:33:51 +0000 (10:33 +0100)
committerFlorian Forster <octo@collectd.org>
Sun, 24 Feb 2013 09:33:51 +0000 (10:33 +0100)
This works around an issue where RRDtool crashes due to two threads
trying to create the same file at the same time. This shouldn't happen
in normal operation, but an attacker could cause this, e.g. with
specially crafted network packets.

src/utils_rrdcreate.c

index e6a2786..3b8f342 100644 (file)
@@ -36,6 +36,14 @@ struct srrd_create_args_s
 };
 typedef struct srrd_create_args_s srrd_create_args_t;
 
+struct async_create_file_s;
+typedef struct async_create_file_s async_create_file_t;
+struct async_create_file_s
+{
+  char *filename;
+  async_create_file_t *next;
+};
+
 /*
  * Private variables
  */
@@ -61,6 +69,9 @@ static int rra_types_num = STATIC_ARRAY_SIZE (rra_types);
 static pthread_mutex_t librrd_lock = PTHREAD_MUTEX_INITIALIZER;
 #endif
 
+static async_create_file_t *async_creation_list = NULL;
+static pthread_mutex_t async_creation_lock = PTHREAD_MUTEX_INITIALIZER;
+
 /*
  * Private functions
  */
@@ -434,24 +445,147 @@ static int srrd_create (const char *filename, /* {{{ */
 } /* }}} int srrd_create */
 #endif /* !HAVE_THREADSAFE_LIBRRD */
 
+static int lock_file (char const *filename) /* {{{ */
+{
+  async_create_file_t *ptr;
+  struct stat sb;
+  int status;
+
+  pthread_mutex_lock (&async_creation_lock);
+
+  for (ptr = async_creation_list; ptr != NULL; ptr = ptr->next)
+    if (strcmp (filename, ptr->filename) == 0)
+      break;
+
+  if (ptr != NULL)
+  {
+    pthread_mutex_unlock (&async_creation_lock);
+    return (EEXIST);
+  }
+
+  errno = 0;
+  status = stat (filename, &sb);
+  if (errno != ENOENT)
+  {
+    pthread_mutex_unlock (&async_creation_lock);
+    return (EEXIST);
+  }
+
+  ptr = malloc (sizeof (*ptr));
+  if (ptr == NULL)
+  {
+    pthread_mutex_unlock (&async_creation_lock);
+    return (ENOMEM);
+  }
+
+  ptr->filename = strdup (filename);
+  if (ptr->filename == NULL)
+  {
+    pthread_mutex_unlock (&async_creation_lock);
+    sfree (ptr);
+    return (ENOMEM);
+  }
+
+  ptr->next = async_creation_list;
+  async_creation_list = ptr;
+
+  pthread_mutex_unlock (&async_creation_lock);
+
+  return (0);
+} /* }}} int lock_file */
+
+static int unlock_file (char const *filename) /* {{{ */
+{
+  async_create_file_t *this;
+  async_create_file_t *prev;
+
+
+  pthread_mutex_lock (&async_creation_lock);
+
+  prev = NULL;
+  for (this = async_creation_list; this != NULL; this = this->next)
+  {
+    if (strcmp (filename, this->filename) == 0)
+      break;
+    prev = this;
+  }
+
+  if (this == NULL)
+  {
+    pthread_mutex_unlock (&async_creation_lock);
+    return (ENOENT);
+  }
+
+  if (prev == NULL)
+  {
+    assert (this == async_creation_list);
+    async_creation_list = this->next;
+  }
+  else
+  {
+    assert (this == prev->next);
+    prev->next = this->next;
+  }
+  this->next = NULL;
+
+  pthread_mutex_unlock (&async_creation_lock);
+
+  sfree (this->filename);
+  sfree (this);
+
+  return (0);
+} /* }}} int unlock_file */
+
 static void *srrd_create_thread (void *targs) /* {{{ */
 {
   srrd_create_args_t *args = targs;
+  char tmpfile[PATH_MAX];
   int status;
 
-  status = srrd_create (args->filename, args->pdp_step, args->last_up,
+  status = lock_file (args->filename);
+  if (status != 0)
+  {
+    if (status == EEXIST)
+      NOTICE ("srrd_create_thread: File \"%s\" is already being created.",
+          args->filename);
+    else
+      ERROR ("srrd_create_thread: Unable to lock file \"%s\".",
+          args->filename);
+    srrd_create_args_destroy (args);
+    return (0);
+  }
+
+  ssnprintf (tmpfile, sizeof (tmpfile), "%s.async", args->filename);
+
+  status = srrd_create (tmpfile, args->pdp_step, args->last_up,
       args->argc, (void *) args->argv);
   if (status != 0)
   {
     WARNING ("srrd_create_thread: srrd_create (%s) returned status %i.",
         args->filename, status);
+    unlink (tmpfile);
+    unlock_file (args->filename);
+    srrd_create_args_destroy (args);
+    return (0);
   }
-  else
+
+  status = rename (tmpfile, args->filename);
+  if (status != 0)
   {
-    DEBUG ("srrd_create_thread: Successfully created RRD file \"%s\".",
-        args->filename);
+    char errbuf[1024];
+    ERROR ("srrd_create_thread: rename (\"%s\", \"%s\") failed: %s",
+        tmpfile, args->filename,
+        sstrerror (errno, errbuf, sizeof (errbuf)));
+    unlink (tmpfile);
+    unlock_file (args->filename);
+    srrd_create_args_destroy (args);
+    return (0);
   }
 
+  DEBUG ("srrd_create_thread: Successfully created RRD file \"%s\".",
+      args->filename);
+
+  unlock_file (args->filename);
   srrd_create_args_destroy (args);
 
   return (0);
@@ -466,6 +600,8 @@ static int srrd_create_async (const char *filename, /* {{{ */
   pthread_attr_t attr;
   int status;
 
+  DEBUG ("srrd_create_async: Creating \"%s\" in the background.", filename);
+
   args = srrd_create_args_create (filename, pdp_step, last_up, argc, argv);
   if (args == NULL)
     return (-1);