CVE-2024-22050 (published 2024-01-04T21:15Z)

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.\n\n

CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N)

Weakness information:
cwe_id: CWE-22
cwe_name: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
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.
extended_description: 
            Many file operations are intended to take place within a restricted directory. By using special elements such as ".." and "/" separators, attackers can escape outside of the restricted location to access files or directories that are elsewhere on the system. One of the most common special elements is the "../" sequence, which in most modern operating systems is interpreted as the parent directory of the current location. This is referred to as relative path traversal. Path traversal also covers the use of absolute pathnames such as "/usr/local/bin" to access unexpected files. This is referred to as absolute path traversal.
         
         

Fix information:
repo_url: https://github.com/boazsegev/iodine
commit_details: repo_url: https://github.com/boazsegev/iodine
author: Bo
author_date: 2019-10-04 23:45:21+03:00
msg: update to facil.io 0.7.3
repository: repo_url: https://github.com/boazsegev/iodine
repo_name: boazsegev/iodine
description: iodine - HTTP / WebSockets Server for Ruby with Pub/Sub support
repo_language: C
owner: boazsegev
file_changes: filename: CHANGELOG.md
change_type: ModificationType.MODIFY
diff: @@ -6,6 +6,10 @@ Please notice that this change log contains changes for upcoming releases as wel
 
 ## 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).

programming_language: Markdown
filename: fio.c
change_type: ModificationType.MODIFY
diff: @@ -1178,12 +1178,13 @@ 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;
   }
@@ -1346,7 +1347,7 @@ 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;
@@ -1415,7 +1416,7 @@ static void sig_int_handler(int sig) {
     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);
 }
 
@@ -1423,7 +1424,7 @@ static void sig_int_handler(int sig) {
 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));
@@ -1457,8 +1458,9 @@ 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);
@@ -2968,7 +2970,7 @@ ssize_t fio_flush(intptr_t uuid) {
     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) {
@@ -3533,11 +3535,12 @@ static void __attribute__((destructor)) fio_lib_destroy(void) {
   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();
@@ -3811,15 +3814,16 @@ static void fio_worker_cleanup(void) {
       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");
@@ -5125,7 +5129,7 @@ struct subscription_s {
   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;
@@ -6202,7 +6206,7 @@ static void fio_cluster_listen_on_close(intptr_t uuid,
                   (int)getpid());
 #endif
     if (fio_data->active)
-      fio_stop();
+      kill(0, SIGINT);
   }
   (void)uuid;
 }
@@ -6244,6 +6248,7 @@ static void fio_cluster_client_handler(struct cluster_pr_s *pr) {
     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 */
@@ -6498,7 +6503,7 @@ 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,

programming_language: C
filename: fio.h
change_type: ModificationType.MODIFY
diff: @@ -109,8 +109,8 @@ 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
@@ -1250,7 +1250,7 @@ inline FIO_FUNC ssize_t fio_write(const intptr_t uuid, const void *buffer,
 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);
 }
 
 /**
@@ -2984,8 +2984,8 @@ FIO_FUNC inline void fio_reschedule_thread(void) {
 
 /** 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);
 }
 
@@ -5494,10 +5494,10 @@ 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.
@@ -5610,16 +5610,16 @@ typedef struct {
 #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 {                                                                         \
@@ -5871,7 +5871,7 @@ FIO_FUNC inline FIO_NAME(_map_s_) *
     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;
@@ -5890,7 +5890,7 @@ FIO_FUNC inline FIO_NAME(_map_s_) *
       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;

programming_language: C
filename: fio_cli.c
change_type: ModificationType.MODIFY
diff: @@ -272,19 +272,19 @@ print_help:
       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;
       }

programming_language: C
filename: fio_tls_missing.c
change_type: ModificationType.MODIFY
diff: @@ -19,7 +19,7 @@ Feel free to copy, use and enjoy according to the license provided.
  */
 #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
@@ -628,6 +628,7 @@ void FIO_TLS_WEAK fio_tls_destroy(fio_tls_s *tls) {
   fio_tls_destroy_context(tls);
   alpn_list_free(&tls->alpn);
   cert_ary_free(&tls->sni);
+  trust_ary_free(&tls->trust);
   free(tls);
 }
 

programming_language: C
filename: fio_tls_openssl.c
change_type: ModificationType.MODIFY
diff: @@ -1005,6 +1005,7 @@ void FIO_TLS_WEAK fio_tls_destroy(fio_tls_s *tls) {
   fio_tls_destroy_context(tls);
   alpn_list_free(&tls->alpn);
   cert_ary_free(&tls->sni);
+  trust_ary_free(&tls->trust);
   free(tls);
 }
 

programming_language: C
filename: fiobj4fio.h
change_type: ModificationType.MODIFY
diff: @@ -14,7 +14,7 @@ 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);
 }
 

programming_language: C
filename: fiobj_numbers.h
change_type: ModificationType.MODIFY
diff: @@ -82,10 +82,12 @@ size_t fio_ltoa(char *dest, int64_t num, uint8_t base);
 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)

programming_language: C
filename: http.c
change_type: ModificationType.MODIFY
diff: @@ -356,6 +356,21 @@ int http_sendfile(http_s *r, int fd, uintptr_t length, uintptr_t offset) {
   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).
  *
@@ -391,14 +406,8 @@ int http_sendfile2(http_s *h, const char *prefix, size_t prefix_len,
       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++;
@@ -408,6 +417,9 @@ int http_sendfile2(http_s *h, const char *prefix, size_t prefix_len,
       }
       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);

programming_language: C
filename: http.h
change_type: ModificationType.MODIFY
diff: @@ -370,7 +370,7 @@ struct http_settings_s {
    *       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;

programming_language: C
filename: http1.c
change_type: ModificationType.MODIFY
diff: @@ -554,7 +554,7 @@ static int http1_on_request(http1_parser_s *parser) {
   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) {
@@ -563,7 +563,7 @@ static int http1_on_response(http1_parser_s *parser) {
   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,
@@ -666,9 +666,9 @@ static int http1_on_body_chunk(http1_parser_s *parser, char *data,
 
 /** 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;
 }
@@ -723,8 +723,8 @@ static inline void http1_consume_data(intptr_t uuid, http1pr_s *p) {
 
 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);
 }
@@ -756,8 +756,8 @@ static void http1_on_close(intptr_t uuid, fio_protocol_s *protocol) {
 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;
@@ -776,7 +776,6 @@ static void http1_on_data_first_time(intptr_t uuid, fio_protocol_s *protocol) {
 
   /* 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);

programming_language: C
filename: iodine_mustache.c
change_type: ModificationType.MODIFY
diff: @@ -73,6 +73,7 @@ static inline VALUE fiobj_mustache_find_obj_absolute(VALUE udata,
   /* 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 */
@@ -295,8 +296,6 @@ static VALUE iodine_mustache_new(int argc, VALUE *argv, VALUE self) {
   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) {

programming_language: C
filename: iodine.gemspec
change_type: ModificationType.MODIFY
diff: @@ -42,5 +42,6 @@ Gem::Specification.new do |spec|
   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

programming_language: Ruby
filename: version.rb
change_type: ModificationType.MODIFY
diff: @@ -1,3 +1,3 @@
 module Iodine
-  VERSION = '0.7.33'.freeze
+  VERSION = '0.7.34'.freeze
 end

programming_language: Ruby