CVE-2024-22050: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') (CWE-22)
value: Path traversal in the static file service in Iodine less than 0.7.33 allows an unauthenticated, remote attacker to read files outside the public folder via malicious URLs.


CWE description: 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.
CVSS v3 base score: 7.5
Published: 2024-01-04T21:15Z
Repository: https://github.com/boazsegev/iodine
Language: C
Files changed: CHANGELOG.md, ext/iodine/fio.c, ext/iodine/fio.h, ext/iodine/fio_cli.c, ext/iodine/fio_tls_missing.c, ext/iodine/fio_tls_openssl.c, ext/iodine/fiobj4fio.h, ext/iodine/fiobj_numbers.h, ext/iodine/http.c, ext/iodine/http.h, ext/iodine/http1.c, ext/iodine/iodine_mustache.c, iodine.gemspec, lib/iodine/version.rb
Commit date: 2019-10-04 23:45:21 +0300
Commit message: update to facil.io 0.7.3

Diff: diff --git a/CHANGELOG.md b/CHANGELOG.md
index 02191aa..616475b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,10 +4,14 @@
 
 Please notice that this change log contains changes for upcoming releases as well. Please refer to the current gem version to review the current release.
 
 ## Changes:
 
+#### Change log v.0.7.34
+
+**Security**: (`facil.io`, `http`) updated to facil.io 0.7.3, incorporating it's bug fixes and security updates.
+
 #### Change log v.0.7.33
 
 **Fix**: (`iodine`) exception protection would fail and crash if the exception throws wasn't of type `Exception`. I'm not sure how this would happen, but on some Ruby versions it appeared to have occur, maybe where a custom `raise` would be called with a non-exception type. The issue was fixed by testing for the availability of the `message` and `backtrace` functions. Credit to Jan Biedermann (@janbiedermann) for exposing this issue (#76).
 
 **Fix**: (`cli`) the CLI interface no longer requires Rack to be installed. If Rack is installed, it will be used. Otherwise, a copy of the Rack::Builder code will execute, licensed under the MIT license from the Rack source code (Copyright (C) 2007-2019 [Leah Neukirchen](http://leahneukirchen.org/infopage.html)).
diff --git a/ext/iodine/fio.c b/ext/iodine/fio.c
index b4745b5..59f0a7e 100644
--- a/ext/iodine/fio.c
+++ b/ext/iodine/fio.c
@@ -1176,16 +1176,17 @@ static inline void fio_mark_time(void) {
 }
 
 /** Calculates the due time for a task, given it's interval */
 static struct timespec fio_timer_calc_due(size_t interval) {
   struct timespec now = fio_last_tick();
-  if (interval > 1000) {
-    now.tv_sec += interval / 1000;
-    interval -= interval / 1000;
+  if (interval >= 1000) {
+    unsigned long long secs = interval / 1000;
+    now.tv_sec += secs;
+    interval -= secs * 1000;
   }
   now.tv_nsec += (interval * 1000000UL);
-  if (now.tv_nsec > 1000000000L) {
+  if (now.tv_nsec >= 1000000000L) {
     now.tv_nsec -= 1000000000L;
     now.tv_sec += 1;
   }
   return now;
 }
@@ -1344,11 +1345,11 @@ Section Start Marker
 
 
 ***************************************************************************** */
 
 volatile uint8_t fio_signal_children_flag = 0;
-
+volatile fio_lock_i fio_signal_set_flag = 0;
 /* store old signal handlers to propegate signal handling */
 static struct sigaction fio_old_sig_chld;
 static struct sigaction fio_old_sig_pipe;
 static struct sigaction fio_old_sig_term;
 static struct sigaction fio_old_sig_int;
@@ -1413,19 +1414,19 @@ static void sig_int_handler(int sig) {
   /* fallthrough */
   default:
     break;
   }
   /* propagate signale handling to previous existing handler (if any) */
-  if (old->sa_handler != SIG_IGN && old->sa_handler != SIG_DFL)
+  if (old && old->sa_handler != SIG_IGN && old->sa_handler != SIG_DFL)
     old->sa_handler(sig);
 }
 
 /* setup handling for the SIGUSR1, SIGPIPE, SIGINT and SIGTERM signals. */
 static void fio_signal_handler_setup(void) {
   /* setup signal handling */
   struct sigaction act;
-  if (fio_old_sig_int.sa_handler)
+  if (fio_trylock(&fio_signal_set_flag))
     return;
 
   memset(&act, 0, sizeof(act));
 
   act.sa_handler = sig_int_handler;
@@ -1455,12 +1456,13 @@ static void fio_signal_handler_setup(void) {
   };
 }
 
 void fio_signal_handler_reset(void) {
   struct sigaction old;
-  if (!fio_old_sig_int.sa_handler)
+  if (fio_signal_set_flag)
     return;
+  fio_unlock(&fio_signal_set_flag);
   memset(&old, 0, sizeof(old));
   sigaction(SIGINT, &fio_old_sig_int, &old);
   sigaction(SIGTERM, &fio_old_sig_term, &old);
   sigaction(SIGPIPE, &fio_old_sig_pipe, &old);
   if (fio_old_sig_chld.sa_handler)
@@ -2966,11 +2968,11 @@ ssize_t fio_flush(intptr_t uuid) {
                                            uuid_data(uuid).packet);
   if (tmp <= 0) {
     goto test_errno;
   }
 
-  if (uuid_data(uuid).packet_count >= 1024 &&
+  if (uuid_data(uuid).packet_count >= FIO_SLOWLORIS_LIMIT &&
       uuid_data(uuid).packet == old_packet &&
       uuid_data(uuid).sent >= old_sent &&
       (uuid_data(uuid).sent - old_sent) < 32768) {
     /* Slowloris attack assumed */
     goto attacked;
@@ -3531,15 +3533,16 @@ static void fio_mem_destroy(void);
 static void __attribute__((destructor)) fio_lib_destroy(void) {
   uint8_t add_eol = fio_is_master();
   fio_data->active = 0;
   fio_on_fork();
   fio_defer_perform();
+  fio_timer_clear_all();
+  fio_defer_perform();
   fio_state_callback_force(FIO_CALL_AT_EXIT);
   fio_state_callback_clear_all();
   fio_defer_perform();
   fio_poll_close();
-  fio_timer_clear_all();
   fio_free(fio_data);
   /* memory library destruction must be last */
   fio_mem_destroy();
   FIO_LOG_DEBUG("(%d) facil.io resources released, exit complete.",
                 (int)getpid());
@@ -3809,19 +3812,20 @@ static void fio_worker_cleanup(void) {
   for (size_t i = 0; i <= fio_data->max_protocol_fd; ++i) {
     if (fd_data(i).protocol || fd_data(i).open) {
       fio_force_close(fd2uuid(i));
     }
   }
-  fio_defer_perform();
-  fio_state_callback_force(FIO_CALL_ON_FINISH);
+  fio_timer_clear_all();
   fio_defer_perform();
   if (!fio_data->is_worker) {
-    fio_cluster_signal_children();
+    kill(0, SIGINT);
     while (wait(NULL) != -1)
       ;
   }
   fio_defer_perform();
+  fio_state_callback_force(FIO_CALL_ON_FINISH);
+  fio_defer_perform();
   fio_signal_handler_reset();
   if (fio_data->parent == getpid()) {
     FIO_LOG_INFO("   ---  Shutdown Complete  ---\n");
   } else {
     FIO_LOG_INFO("(%d) cleanup complete.", (int)getpid());
@@ -5123,11 +5127,11 @@ struct subscription_s {
   void (*on_message)(fio_msg_s *msg);
   void (*on_unsubscribe)(void *udata1, void *udata2);
   void *udata1;
   void *udata2;
   /** reference counter. */
-  uintptr_t ref;
+  volatile uintptr_t ref;
   /** prevents the callback from running concurrently for multiple messages. */
   fio_lock_i lock;
   fio_lock_i unsubscribed;
 };
 
@@ -6200,11 +6204,11 @@ static void fio_cluster_listen_on_close(intptr_t uuid,
 #if DEBUG
     FIO_LOG_DEBUG("(%d) stopped listening for cluster connections",
                   (int)getpid());
 #endif
     if (fio_data->active)
-      fio_stop();
+      kill(0, SIGINT);
   }
   (void)uuid;
 }
 
 static void fio_listen2cluster(void *ignore) {
@@ -6242,10 +6246,11 @@ static void fio_cluster_client_handler(struct cluster_pr_s *pr) {
   case FIO_CLUSTER_MSG_JSON:
     fio_publish2process(fio_msg_internal_dup(pr->msg));
     break;
   case FIO_CLUSTER_MSG_SHUTDOWN:
     fio_stop();
+    kill(getpid(), SIGINT);
   case FIO_CLUSTER_MSG_ERROR:         /* fallthrough */
   case FIO_CLUSTER_MSG_PING:          /* fallthrough */
   case FIO_CLUSTER_MSG_ROOT:          /* fallthrough */
   case FIO_CLUSTER_MSG_ROOT_JSON:     /* fallthrough */
   case FIO_CLUSTER_MSG_PUBSUB_SUB:    /* fallthrough */
@@ -6496,11 +6501,11 @@ static void fio_pubsub_on_fork(void) {
  **************************************************************************** */
 
 /** Signals children (or self) to shutdown) - NOT signal safe. */
 static void fio_cluster_signal_children(void) {
   if (fio_parent_pid() != getpid()) {
-    fio_stop();
+    kill(getpid(), SIGINT);
     return;
   }
   fio_cluster_server_sender(fio_msg_internal_create(0, FIO_CLUSTER_MSG_SHUTDOWN,
                                                     (fio_str_info_s){.len = 0},
                                                     (fio_str_info_s){.len = 0},
diff --git a/ext/iodine/fio.h b/ext/iodine/fio.h
index 03ceedf..0f7b8c1 100644
--- a/ext/iodine/fio.h
+++ b/ext/iodine/fio.h
@@ -107,12 +107,12 @@ Feel free to copy, use and enjoy according to the license provided.
 Version and helper macros
 ***************************************************************************** */
 
 #define FIO_VERSION_MAJOR 0
 #define FIO_VERSION_MINOR 7
-#define FIO_VERSION_PATCH 0
-#define FIO_VERSION_BETA 9
+#define FIO_VERSION_PATCH 3
+#define FIO_VERSION_BETA 0
 
 /* Automatically convert version data to a string constant - ignore these two */
 #define FIO_MACRO2STR_STEP2(macro) #macro
 #define FIO_MACRO2STR(macro) FIO_MACRO2STR_STEP2(macro)
 
@@ -1248,11 +1248,11 @@ inline FIO_FUNC ssize_t fio_write(const intptr_t uuid, const void *buffer,
  * Returns -1 and closes the file on error. Returns 0 on success.
  */
 inline FIO_FUNC ssize_t fio_sendfile(intptr_t uuid, intptr_t source_fd,
                                      off_t offset, size_t length) {
   return fio_write2(uuid, .data.fd = source_fd, .length = length, .is_fd = 1,
-                    .offset = offset);
+                    .offset = (uintptr_t)offset);
 }
 
 /**
  * Returns the number of `fio_write` calls that are waiting in the socket's
  * queue and haven't been processed.
@@ -2982,12 +2982,12 @@ FIO_FUNC inline void fio_reschedule_thread(void) {
   nanosleep(&tm, NULL);
 }
 
 /** Nanosleep the thread - a blocking throttle. */
 FIO_FUNC inline void fio_throttle_thread(size_t nano_sec) {
-  const struct timespec tm = {.tv_nsec = (nano_sec % 1000000000),
-                              .tv_sec = (nano_sec / 1000000000)};
+  const struct timespec tm = {.tv_nsec = (long)(nano_sec % 1000000000),
+                              .tv_sec = (time_t)(nano_sec / 1000000000)};
   nanosleep(&tm, NULL);
 }
 
 /** returns 0 if the lock was acquired and another value on failure. */
 FIO_FUNC inline int fio_trylock(fio_lock_i *lock) {
@@ -5492,14 +5492,14 @@ Done
  *
  *
  * Note: FIO_SET_HASH_TYPE should, normaly be left alone (uintptr_t is
  *       enough). Also, the hash value 0 is reserved to indicate an empty slot.
  *
- * Note: the FIO_SET_OBJ_COMPARE for Sets or the FIO_SET_KEY_COMPARE will be
- *       used to compare against invalid as well as valid objects. Invalid
- *       objects have their bytes all zero. FIO_SET_*_DESTROY should somehow
- *       mark them as invalid.
+ * Note: the FIO_SET_OBJ_COMPARE or the FIO_SET_KEY_COMPARE will be used to
+ *       compare against invalid as well as valid objects. Invalid objects have
+ *       their bytes all zero. FIO_SET_*_DESTROY should somehow mark them as
+ *       invalid.
  *
  * Note: Before freeing the Set, FIO_SET_OBJ_DESTROY will be automatically
  *       called for every existing object.
  */
 
@@ -5608,20 +5608,20 @@ typedef struct {
 #ifndef FIO_SET_KEY_DESTROY
 #define FIO_SET_KEY_DESTROY(obj) ((void)0)
 #endif
 
 /* The default Hash Map-Set has will use straight euqality operators */
-#if !defined(FIO_SET_KEY_COMPARE)
+#ifndef FIO_SET_KEY_COMPARE
 #define FIO_SET_KEY_COMPARE(o1, o2) ((o1) == (o2))
 #endif
 
 /** Internal macros for object actions in Hash mode */
 #define FIO_SET_COMPARE(o1, o2) FIO_SET_KEY_COMPARE((o1).key, (o2).key)
-#define FIO_SET_COPY(dest, org)                                                \
+#define FIO_SET_COPY(dest, src)                                                \
   do {                                                                         \
-    FIO_SET_OBJ_COPY((dest).obj, (org).obj);                                   \
-    FIO_SET_KEY_COPY((dest).key, (org).key);                                   \
+    FIO_SET_OBJ_COPY((dest).obj, (src).obj);                                   \
+    FIO_SET_KEY_COPY((dest).key, (src).key);                                   \
   } while (0);
 #define FIO_SET_DESTROY(couplet)                                               \
   do {                                                                         \
     FIO_SET_KEY_DESTROY((couplet).key);                                        \
     FIO_SET_OBJ_DESTROY((couplet).obj);                                        \
@@ -5869,11 +5869,11 @@ FIO_FUNC inline FIO_NAME(_map_s_) *
     /* O(1) access to object */
     pos = set->map + (hash_alt & mask);
     if (FIO_SET_HASH_COMPARE(FIO_SET_HASH_INVALID, pos->hash))
       return pos;
     if (FIO_SET_HASH_COMPARE(pos->hash, hash_value_i)) {
-      if (!pos->pos || FIO_SET_COMPARE(pos->pos->obj, obj))
+      if (!pos->pos || (FIO_SET_COMPARE(pos->pos->obj, obj)))
         return pos;
       /* full hash value collision detected */
       set->has_collisions = 1;
       ++full_collisions_counter;
     }
@@ -5888,11 +5888,11 @@ FIO_FUNC inline FIO_NAME(_map_s_) *
       i += FIO_SET_CUCKOO_STEPS;
       pos = set->map + ((hash_alt + i) & mask);
       if (FIO_SET_HASH_COMPARE(FIO_SET_HASH_INVALID, pos->hash))
         return pos;
       if (FIO_SET_HASH_COMPARE(pos->hash, hash_value_i)) {
-        if (!pos->pos || FIO_SET_COMPARE(pos->pos->obj, obj))
+        if (!pos->pos || (FIO_SET_COMPARE(pos->pos->obj, obj)))
           return pos;
         /* full hash value collision detected */
         set->has_collisions = 1;
         if (++full_collisions_counter >= FIO_SET_MAX_MAP_FULL_COLLISIONS) {
           /* is the hash under attack? */
diff --git a/ext/iodine/fio_cli.c b/ext/iodine/fio_cli.c
index bed978e..f9f3d77 100644
--- a/ext/iodine/fio_cli.c
+++ b/ext/iodine/fio_cli.c
@@ -270,23 +270,23 @@ print_help:
       if (padding < 0)
         padding = 0;
       switch ((size_t)type) {
       case FIO_CLI_STRING__TYPE_I:
         fprintf(stderr,
-                " \x1B[1m%.*s\x1B[0m\x1B[2m <>\x1B[0m%*s\t\x1B[2msame as "
-                "%.*s\x1B[0m\n",
+                " \x1B[1m%.*s\x1B[0m\x1B[2m <>\x1B[0m%*s\t(same as "
+                "\x1B[1m%.*s\x1B[0m)\n",
                 (int)(tmp - start), p + start, padding, "", first_len, p);
         break;
       case FIO_CLI_BOOL__TYPE_I:
         fprintf(stderr,
-                " \x1B[1m%.*s\x1B[0m   %*s\t\x1B[2msame as %.*s\x1B[0m\n",
+                " \x1B[1m%.*s\x1B[0m   %*s\t(same as \x1B[1m%.*s\x1B[0m)\n",
                 (int)(tmp - start), p + start, padding, "", first_len, p);
         break;
       case FIO_CLI_INT__TYPE_I:
         fprintf(stderr,
-                " \x1B[1m%.*s\x1B[0m\x1B[2m ##\x1B[0m%*s\t\x1B[2msame as "
-                "%.*s\x1B[0m\n",
+                " \x1B[1m%.*s\x1B[0m\x1B[2m ##\x1B[0m%*s\t(same as "
+                "\x1B[1m%.*s\x1B[0m)\n",
                 (int)(tmp - start), p + start, padding, "", first_len, p);
         break;
       }
     }
 
diff --git a/ext/iodine/fio_tls_missing.c b/ext/iodine/fio_tls_missing.c
index 4dbbd9d..6c8b111 100644
--- a/ext/iodine/fio_tls_missing.c
+++ b/ext/iodine/fio_tls_missing.c
@@ -17,11 +17,11 @@ Feel free to copy, use and enjoy according to the license provided.
  * This implementation is optimized for ease of development rather than memory
  * consumption.
  */
 #include "fio_tls.h"
 
-#if 1 /* TODO: place library compiler flags here */
+#if !defined(FIO_TLS_FOUND) /* Library compiler flags */
 
 #define REQUIRE_LIBRARY()
 #define FIO_TLS_WEAK
 
 /* TODO: delete me! */
@@ -626,9 +626,10 @@ void FIO_TLS_WEAK fio_tls_destroy(fio_tls_s *tls) {
   if (fio_atomic_sub(&tls->ref, 1))
     return;
   fio_tls_destroy_context(tls);
   alpn_list_free(&tls->alpn);
   cert_ary_free(&tls->sni);
+  trust_ary_free(&tls->trust);
   free(tls);
 }
 
 #endif /* Library compiler flags */
diff --git a/ext/iodine/fio_tls_openssl.c b/ext/iodine/fio_tls_openssl.c
index a68917c..a49d838 100644
--- a/ext/iodine/fio_tls_openssl.c
+++ b/ext/iodine/fio_tls_openssl.c
@@ -1003,9 +1003,10 @@ void FIO_TLS_WEAK fio_tls_destroy(fio_tls_s *tls) {
   if (fio_atomic_sub(&tls->ref, 1))
     return;
   fio_tls_destroy_context(tls);
   alpn_list_free(&tls->alpn);
   cert_ary_free(&tls->sni);
+  trust_ary_free(&tls->trust);
   free(tls);
 }
 
 #endif /* Library compiler flags */
diff --git a/ext/iodine/fiobj4fio.h b/ext/iodine/fiobj4fio.h
index b03595e..86415d9 100644
--- a/ext/iodine/fiobj4fio.h
+++ b/ext/iodine/fiobj4fio.h
@@ -12,10 +12,10 @@ static void fiobj4sock_dealloc(void *o) { fiobj_free((FIOBJ)o); }
 /** send a FIOBJ  object through a socket. */
 static inline __attribute__((unused)) ssize_t fiobj_send_free(intptr_t uuid,
                                                               FIOBJ o) {
   fio_str_info_s s = fiobj_obj2cstr(o);
   return fio_write2(uuid, .data.buffer = (void *)(o),
-                    .offset = (((intptr_t)s.data) - ((intptr_t)(o))),
+                    .offset = (uintptr_t)(((intptr_t)s.data) - ((intptr_t)(o))),
                     .length = s.len, .after.dealloc = fiobj4sock_dealloc);
 }
 
 #endif
diff --git a/ext/iodine/fiobj_numbers.h b/ext/iodine/fiobj_numbers.h
index cae361b..a805ad4 100644
--- a/ext/iodine/fiobj_numbers.h
+++ b/ext/iodine/fiobj_numbers.h
@@ -80,14 +80,16 @@ size_t fio_ltoa(char *dest, int64_t num, uint8_t base);
  * Returns the number of bytes actually written (excluding the NUL terminator).
  */
 size_t fio_ftoa(char *dest, double num, uint8_t base);
 
 /** Converts a number to a temporary, thread safe, C string object */
-fio_str_info_s fio_ltocstr(long);
+fio_str_info_s __attribute__((deprecated("use local buffer with fio_ltoa")))
+fio_ltocstr(long);
 
 /** Converts a float to a temporary, thread safe, C string object */
-fio_str_info_s fio_ftocstr(double);
+fio_str_info_s __attribute__((deprecated("use local buffer with fio_ftoa")))
+fio_ftocstr(double);
 
 /* *****************************************************************************
 Pointer Wrapping Helper MACROs (uses integers)
 ***************************************************************************** */
 
diff --git a/ext/iodine/http.c b/ext/iodine/http.c
index 1991261..18d7f4a 100644
--- a/ext/iodine/http.c
+++ b/ext/iodine/http.c
@@ -354,10 +354,25 @@ int http_sendfile(http_s *r, int fd, uintptr_t length, uintptr_t offset) {
   add_content_type(r);
   add_date(r);
   return ((http_vtable_s *)r->private_data.vtbl)
       ->http_sendfile(r, fd, length, offset);
 }
+
+static inline int http_test_encoded_path(const char *mem, size_t len) {
+  const char *pos = NULL;
+  const char *end = mem + len;
+  while (mem < end && (pos = memchr(mem, '/', (size_t)len))) {
+    len = end - pos;
+    mem = pos + 1;
+    if (pos[1] == '/')
+      return -1;
+    if (len > 3 && pos[1] == '.' && pos[2] == '.' && pos[3] == '/')
+      return -1;
+  }
+  return 0;
+}
+
 /**
  * Sends the response headers and the specified file (the response's body).
  *
  * Returns -1 eton error and 0 on success.
  *
@@ -389,27 +404,24 @@ int http_sendfile2(http_s *h, const char *prefix, size_t prefix_len,
     fio_str_info_s tmp = fiobj_obj2cstr(filename);
     if (encoded) {
       char *pos = (char *)encoded;
       const char *end = encoded + encoded_len;
       while (pos < end) {
-        /* test for path manipulations while decoding */
-        if (*pos == '/' && (pos[1] == '/' ||
-                            (((uintptr_t)end - (uintptr_t)pos >= 4) &&
-                             pos[1] == '.' && pos[2] == '.' && pos[3] == '/')))
-          return -1;
         if (*pos == '%') {
-          // decode hex value
-          // this is a percent encoded value.
+          // decode hex value (this is a percent encoded value).
           if (hex2byte((uint8_t *)tmp.data + tmp.len, (uint8_t *)pos + 1))
             return -1;
           tmp.len++;
           pos += 3;
         } else
           tmp.data[tmp.len++] = *(pos++);
       }
       tmp.data[tmp.len] = 0;
       fiobj_str_resize(filename, tmp.len);
+      /* test for path manipulations after decoding */
+      if (http_test_encoded_path(tmp.data + prefix_len, tmp.len - prefix_len))
+        return -1;
     }
     if (tmp.data[tmp.len - 1] == '/')
       fiobj_str_write(filename, "index.html", 10);
   }
   /* test for file existance  */
diff --git a/ext/iodine/http.h b/ext/iodine/http.h
index bf7dfa0..e13a8e2 100644
--- a/ext/iodine/http.h
+++ b/ext/iodine/http.h
@@ -368,11 +368,11 @@ struct http_settings_s {
    * Note: clients, by the nature of socket programming, are counted according
    *       to their internal file descriptor (`fd`) value. Open files and other
    *       sockets count towards a server's limit.
    */
   intptr_t max_clients;
-  /** reserved for future SSL/TLS support. */
+  /** SSL/TLS support. */
   void *tls;
   /** reserved for future use. */
   intptr_t reserved1;
   /** reserved for future use. */
   intptr_t reserved2;
diff --git a/ext/iodine/http1.c b/ext/iodine/http1.c
index f141c86..2aff665 100644
--- a/ext/iodine/http1.c
+++ b/ext/iodine/http1.c
@@ -552,20 +552,20 @@ static int http1_on_request(http1_parser_s *parser) {
   http1pr_s *p = parser2http(parser);
   http_on_request_handler______internal(&http1_pr2handle(p), p->p.settings);
   if (p->request.method && !p->stop)
     http_finish(&p->request);
   h1_reset(p);
-  return !p->close && fio_is_closed(p->p.uuid);
+  return fio_is_closed(p->p.uuid);
 }
 /** called when a response was received. */
 static int http1_on_response(http1_parser_s *parser) {
   http1pr_s *p = parser2http(parser);
   http_on_response_handler______internal(&http1_pr2handle(p), p->p.settings);
   if (p->request.status_str && !p->stop)
     http_finish(&p->request);
   h1_reset(p);
-  return !p->close && fio_is_closed(p->p.uuid);
+  return fio_is_closed(p->p.uuid);
 }
 /** called when a request method is parsed. */
 static int http1_on_method(http1_parser_s *parser, char *method,
                            size_t method_len) {
   http1_pr2handle(parser2http(parser)).method =
@@ -664,13 +664,13 @@ static int http1_on_body_chunk(http1_parser_s *parser, char *data,
   return 0;
 }
 
 /** called when a protocol error occurred. */
 static int http1_on_error(http1_parser_s *parser) {
-  FIO_LOG_DEBUG("HTTP parser error at HTTP/1.1 buffer position %zu/%zu",
-                parser->state.next - parser2http(parser)->buf,
-                parser2http(parser)->buf_len);
+  if (parser2http(parser)->close)
+    return -1;
+  FIO_LOG_DEBUG("HTTP parser error.");
   fio_close(parser2http(parser)->p.uuid);
   return -1;
 }
 
 /* *****************************************************************************
@@ -721,12 +721,12 @@ static inline void http1_consume_data(intptr_t uuid, http1pr_s *p) {
   }
   return;
 
 throttle:
   /* throttle busy clients (slowloris) */
-  fio_suspend(uuid);
   p->stop |= 4;
+  fio_suspend(uuid);
   FIO_LOG_DEBUG("(HTTP/1,1) throttling client at %.*s",
                 (int)fio_peer_addr(uuid).len, fio_peer_addr(uuid).data);
 }
 
 /** called when a data is available, but will not run concurrently */
@@ -754,12 +754,12 @@ static void http1_on_close(intptr_t uuid, fio_protocol_s *protocol) {
 
 /** called when the connection was closed, but will not run concurrently */
 static void http1_on_ready(intptr_t uuid, fio_protocol_s *protocol) {
   /* resume slow clients from suspension */
   http1pr_s *p = (http1pr_s *)protocol;
-  if ((p->stop & 4)) {
-    p->stop ^= 4;
+  if (p->stop & 4) {
+    p->stop ^= 4; /* flip back the bit, so it's zero */
     fio_force_event(uuid, FIO_EVENT_ON_DATA);
   }
   (void)protocol;
 }
 
@@ -774,11 +774,10 @@ static void http1_on_data_first_time(intptr_t uuid, fio_protocol_s *protocol) {
     return;
   p->buf_len += i;
 
   /* ensure future reads skip this first time HTTP/2.0 test */
   p->p.protocol.on_data = http1_on_data;
-  /* Test fot HTTP/2.0 pre-knowledge */
   if (i >= 24 && !memcmp(p->buf, "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", 24)) {
     FIO_LOG_WARNING("client claimed unsupported HTTP/2 prior knowledge.");
     fio_close(uuid);
     return;
   }
diff --git a/ext/iodine/iodine_mustache.c b/ext/iodine/iodine_mustache.c
index b31bb8b..c391f5c 100644
--- a/ext/iodine/iodine_mustache.c
+++ b/ext/iodine/iodine_mustache.c
@@ -71,10 +71,11 @@ static inline VALUE fiobj_mustache_find_obj_absolute(VALUE udata,
   if (tmp != Qundef)
     return tmp;
   /* search by String */
   key = rb_sym2str(key);
   tmp = rb_hash_lookup2(udata, key, Qundef);
+  rb_str_free(key);
   if (tmp != Qundef)
     return tmp;
   /* search by method */
   tmp = Qnil;
   if (rb_respond_to(udata, name_id)) {
@@ -293,12 +294,10 @@ static VALUE iodine_mustache_new(int argc, VALUE *argv, VALUE self) {
   if (template != Qnil)
     Check_Type(template, T_STRING);
   if (filename != Qnil)
     Check_Type(filename, T_STRING);
 
-  fio_str_s str = FIO_STR_INIT;
-
   mustache_s **m = NULL;
   TypedData_Get_Struct(self, mustache_s *, &iodine_mustache_data_type, m);
   if (!m) {
     rb_raise(rb_eRuntimeError, "Iodine::Mustache allocation error.");
   }
diff --git a/iodine.gemspec b/iodine.gemspec
index 02223f1..87c175f 100644
--- a/iodine.gemspec
+++ b/iodine.gemspec
@@ -40,7 +40,8 @@ Gem::Specification.new do |spec|
   # spec.add_development_dependency 'bundler', '>= 1.10', '< 2.0'
   spec.add_development_dependency 'rake', '~> 12.0', '< 13.0'
   spec.add_development_dependency 'minitest', '>=5', '< 6.0'
   spec.add_development_dependency 'rake-compiler', '>= 1', '< 2.0'
 
-  spec.post_install_message = "Thank you for installing Iodine #{Iodine::VERSION}.\n"
+  spec.post_install_message = "Thank you for installing Iodine #{Iodine::VERSION}.\n" +
+                              "Remember: if iodine supports your business, it's is only fair to give value back (code contributions / donations)."
 end
diff --git a/lib/iodine/version.rb b/lib/iodine/version.rb
index 89ed25e..6039f16 100644
--- a/lib/iodine/version.rb
+++ b/lib/iodine/version.rb
@@ -1,3 +1,3 @@
 module Iodine
-  VERSION = '0.7.33'.freeze
+  VERSION = '0.7.34'.freeze
 end

Vulnerable code:   if (interval > 1000) {
    now.tv_sec += interval / 1000;
    interval -= interval / 1000;
  if (now.tv_nsec > 1000000000L) {

  if (old->sa_handler != SIG_IGN && old->sa_handler != SIG_DFL)
  if (fio_old_sig_int.sa_handler)
  if (!fio_old_sig_int.sa_handler)
  if (uuid_data(uuid).packet_count >= 1024 &&
  fio_timer_clear_all();
  fio_defer_perform();
  fio_state_callback_force(FIO_CALL_ON_FINISH);
    fio_cluster_signal_children();
  uintptr_t ref;
      fio_stop();
    fio_stop();
#define FIO_VERSION_PATCH 0
#define FIO_VERSION_BETA 9
                    .offset = offset);
  const struct timespec tm = {.tv_nsec = (nano_sec % 1000000000),
                              .tv_sec = (nano_sec / 1000000000)};
 * Note: the FIO_SET_OBJ_COMPARE for Sets or the FIO_SET_KEY_COMPARE will be
 *       used to compare against invalid as well as valid objects. Invalid
 *       objects have their bytes all zero. FIO_SET_*_DESTROY should somehow
 *       mark them as invalid.
#if !defined(FIO_SET_KEY_COMPARE)
#define FIO_SET_COPY(dest, org)                                                \
    FIO_SET_OBJ_COPY((dest).obj, (org).obj);                                   \
    FIO_SET_KEY_COPY((dest).key, (org).key);                                   \
      if (!pos->pos || FIO_SET_COMPARE(pos->pos->obj, obj))
        if (!pos->pos || FIO_SET_COMPARE(pos->pos->obj, obj))
                " \x1B[1m%.*s\x1B[0m\x1B[2m <>\x1B[0m%*s\t\x1B[2msame as "
                "%.*s\x1B[0m\n",
                " \x1B[1m%.*s\x1B[0m   %*s\t\x1B[2msame as %.*s\x1B[0m\n",
                " \x1B[1m%.*s\x1B[0m\x1B[2m ##\x1B[0m%*s\t\x1B[2msame as "
                "%.*s\x1B[0m\n",
#if 1 /* TODO: place library compiler flags here */
                    .offset = (((intptr_t)s.data) - ((intptr_t)(o))),
fio_str_info_s fio_ltocstr(long);
fio_str_info_s fio_ftocstr(double);
        /* test for path manipulations while decoding */
        if (*pos == '/' && (pos[1] == '/' ||
                            (((uintptr_t)end - (uintptr_t)pos >= 4) &&
                             pos[1] == '.' && pos[2] == '.' && pos[3] == '/')))
          return -1;
          // decode hex value
          // this is a percent encoded value.
  /** reserved for future SSL/TLS support. */
  return !p->close && fio_is_closed(p->p.uuid);
  return !p->close && fio_is_closed(p->p.uuid);
  FIO_LOG_DEBUG("HTTP parser error at HTTP/1.1 buffer position %zu/%zu",
                parser->state.next - parser2http(parser)->buf,
                parser2http(parser)->buf_len);
  fio_suspend(uuid);
  if ((p->stop & 4)) {
    p->stop ^= 4;
  /* Test fot HTTP/2.0 pre-knowledge */
  fio_str_s str = FIO_STR_INIT;

  spec.post_install_message = "Thank you for installing Iodine #{Iodine::VERSION}.\n"
  VERSION = '0.7.33'.freeze
Fixed code: #### Change log v.0.7.34

**Security**: (`facil.io`, `http`) updated to facil.io 0.7.3, incorporating it's bug fixes and security updates.

  if (interval >= 1000) {
    unsigned long long secs = interval / 1000;
    now.tv_sec += secs;
    interval -= secs * 1000;
  if (now.tv_nsec >= 1000000000L) {
volatile fio_lock_i fio_signal_set_flag = 0;
  if (old && old->sa_handler != SIG_IGN && old->sa_handler != SIG_DFL)
  if (fio_trylock(&fio_signal_set_flag))
  if (fio_signal_set_flag)
  fio_unlock(&fio_signal_set_flag);
  if (uuid_data(uuid).packet_count >= FIO_SLOWLORIS_LIMIT &&
  fio_timer_clear_all();
  fio_defer_perform();
  fio_timer_clear_all();
    kill(0, SIGINT);
  fio_state_callback_force(FIO_CALL_ON_FINISH);
  fio_defer_perform();
  volatile uintptr_t ref;
      kill(0, SIGINT);
    kill(getpid(), SIGINT);
    kill(getpid(), SIGINT);
#define FIO_VERSION_PATCH 3
#define FIO_VERSION_BETA 0
                    .offset = (uintptr_t)offset);
  const struct timespec tm = {.tv_nsec = (long)(nano_sec % 1000000000),
                              .tv_sec = (time_t)(nano_sec / 1000000000)};
 * Note: the FIO_SET_OBJ_COMPARE or the FIO_SET_KEY_COMPARE will be used to
 *       compare against invalid as well as valid objects. Invalid objects have
 *       their bytes all zero. FIO_SET_*_DESTROY should somehow mark them as
 *       invalid.
#ifndef FIO_SET_KEY_COMPARE
#define FIO_SET_COPY(dest, src)                                                \
    FIO_SET_OBJ_COPY((dest).obj, (src).obj);                                   \
    FIO_SET_KEY_COPY((dest).key, (src).key);                                   \
      if (!pos->pos || (FIO_SET_COMPARE(pos->pos->obj, obj)))
        if (!pos->pos || (FIO_SET_COMPARE(pos->pos->obj, obj)))
                " \x1B[1m%.*s\x1B[0m\x1B[2m <>\x1B[0m%*s\t(same as "
                "\x1B[1m%.*s\x1B[0m)\n",
                " \x1B[1m%.*s\x1B[0m   %*s\t(same as \x1B[1m%.*s\x1B[0m)\n",
                " \x1B[1m%.*s\x1B[0m\x1B[2m ##\x1B[0m%*s\t(same as "
                "\x1B[1m%.*s\x1B[0m)\n",
#if !defined(FIO_TLS_FOUND) /* Library compiler flags */
  trust_ary_free(&tls->trust);
  trust_ary_free(&tls->trust);
                    .offset = (uintptr_t)(((intptr_t)s.data) - ((intptr_t)(o))),
fio_str_info_s __attribute__((deprecated("use local buffer with fio_ltoa")))
fio_ltocstr(long);
fio_str_info_s __attribute__((deprecated("use local buffer with fio_ftoa")))
fio_ftocstr(double);

static inline int http_test_encoded_path(const char *mem, size_t len) {
  const char *pos = NULL;
  const char *end = mem + len;
  while (mem < end && (pos = memchr(mem, '/', (size_t)len))) {
    len = end - pos;
    mem = pos + 1;
    if (pos[1] == '/')
      return -1;
    if (len > 3 && pos[1] == '.' && pos[2] == '.' && pos[3] == '/')
      return -1;
  }
  return 0;
}

          // decode hex value (this is a percent encoded value).
      /* test for path manipulations after decoding */
      if (http_test_encoded_path(tmp.data + prefix_len, tmp.len - prefix_len))
        return -1;
  /** SSL/TLS support. */
  return fio_is_closed(p->p.uuid);
  return fio_is_closed(p->p.uuid);
  if (parser2http(parser)->close)
    return -1;
  FIO_LOG_DEBUG("HTTP parser error.");
  fio_suspend(uuid);
  if (p->stop & 4) {
    p->stop ^= 4; /* flip back the bit, so it's zero */
  rb_str_free(key);
  spec.post_install_message = "Thank you for installing Iodine #{Iodine::VERSION}.\n" +
                              "Remember: if iodine supports your business, it's is only fair to give value back (code contributions / donations)."
  VERSION = '0.7.34'.freeze