CVE-2018-14363 (published 2018-07-17T17:29Z)

An issue was discovered in NeoMutt before 2018-07-16. newsrc.c does not properly restrict '/' characters that may have unsafe interaction with cache pathnames.

Severity: MEDIUM
CVSS v2 base score: 5.0 (AV:N/AC:L/Au:N/C:N/I:P/A:N)
CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/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/neomutt/neomutt
commit_details: repo_url: https://github.com/neomutt/neomutt
author: Richard Russon
author_date: 2018-07-05 13:32:17+01:00
msg: sanitise cache paths

Co-authored-by: JerikoOne <jeriko.one@gmx.us>
repository: repo_url: https://github.com/neomutt/neomutt
repo_name: neomutt/neomutt
description: ✉️ Teaching an Old Dog New Tricks -- IRC: #neomutt on irc.libera.chat
repo_language: C
owner: neomutt
file_changes: filename: newsrc.c
change_type: ModificationType.MODIFY
diff: @@ -715,7 +715,18 @@ int nntp_active_save_cache(struct NntpServer *nserv)
  */
 static int nntp_hcache_namer(const char *path, char *dest, size_t destlen)
 {
-  return snprintf(dest, destlen, "%s.hcache", path);
+  int count = snprintf(dest, destlen, "%s.hcache", path);
+
+  /* Strip out any directories in the path */
+  char *first = strchr(dest, '/');
+  char *last = strrchr(dest, '/');
+  if (first && last && (last > first))
+  {
+    memmove(first, last, strlen(last) + 1);
+    count -= (last - first);
+  }
+
+  return count;
 }
 
 /**

programming_language: C
filename: pop.c
change_type: ModificationType.MODIFY
diff: @@ -63,6 +63,23 @@
 #define HC_FEXT "hcache"   /* extension for hcache as POP lacks paths */
 #endif
 
+/**
+ * cache_id - Make a message-cache-compatible id
+ * @param id POP message id
+ * @retval ptr Sanitised string
+ *
+ * The POP message id may contain '/' and other awkward characters.
+ *
+ * @note This function returns a pointer to a static buffer.
+ */
+static const char *cache_id(const char *id)
+{
+  static char clean[SHORT_STRING];
+  mutt_str_strfcpy(clean, id, sizeof(clean));
+  mutt_file_sanitize_filename(clean, true);
+  return clean;
+}
+
 /**
  * fetch_message - write line to file
  * @param line String to write
@@ -242,7 +259,7 @@ static int msg_cache_check(const char *id, struct BodyCache *bcache, void *data)
   /* message not found in context -> remove it from cache
    * return the result of bcache, so we stop upon its first error
    */
-  return mutt_bcache_del(bcache, id);
+  return mutt_bcache_del(bcache, cache_id(id));
 }
 
 #ifdef USE_HCACHE
@@ -407,7 +424,7 @@ static int pop_fetch_headers(struct Context *ctx)
        *        - if we don't have a body: new
        */
       const bool bcached =
-          (mutt_bcache_exists(pop_data->bcache, ctx->hdrs[i]->data) == 0);
+          (mutt_bcache_exists(pop_data->bcache, cache_id(ctx->hdrs[i]->data)) == 0);
       ctx->hdrs[i]->old = false;
       ctx->hdrs[i]->read = false;
       if (hcached)
@@ -597,7 +614,7 @@ static int pop_fetch_message(struct Context *ctx, struct Message *msg, int msgno
   unsigned short bcache = 1;
 
   /* see if we already have the message in body cache */
-  msg->fp = mutt_bcache_get(pop_data->bcache, h->data);
+  msg->fp = mutt_bcache_get(pop_data->bcache, cache_id(h->data));
   if (msg->fp)
     return 0;
 
@@ -644,7 +661,7 @@ static int pop_fetch_message(struct Context *ctx, struct Message *msg, int msgno
                        NetInc, h->content->length + h->content->offset - 1);
 
     /* see if we can put in body cache; use our cache as fallback */
-    msg->fp = mutt_bcache_put(pop_data->bcache, h->data);
+    msg->fp = mutt_bcache_put(pop_data->bcache, cache_id(h->data));
     if (!msg->fp)
     {
       /* no */
@@ -689,7 +706,7 @@ static int pop_fetch_message(struct Context *ctx, struct Message *msg, int msgno
    * portion of the headers, those required for the main display.
    */
   if (bcache)
-    mutt_bcache_commit(pop_data->bcache, h->data);
+    mutt_bcache_commit(pop_data->bcache, cache_id(h->data));
   else
   {
     cache->index = h->index;
@@ -783,7 +800,7 @@ static int pop_sync_mailbox(struct Context *ctx, int *index_hint)
         ret = pop_query(pop_data, buf, sizeof(buf));
         if (ret == 0)
         {
-          mutt_bcache_del(pop_data->bcache, ctx->hdrs[i]->data);
+          mutt_bcache_del(pop_data->bcache, cache_id(ctx->hdrs[i]->data));
 #ifdef USE_HCACHE
           mutt_hcache_delete(hc, ctx->hdrs[i]->data, strlen(ctx->hdrs[i]->data));
 #endif

programming_language: C