CVE-2024-22050 (C, published 2024-01-04T21:15Z)
Description: 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.


Severity: nan (CVSS v3 base score: 7.5)
Weakness: CWE-22 \u2014 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
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.
Repository: https://github.com/boazsegev/iodine
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 message: update to facil.io 0.7.3


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