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

An issue was discovered in Mutt before 1.10.1 and NeoMutt before 2018-07-16. pop.c does not forbid characters that may have unsafe interaction with message-cache pathnames, as demonstrated by a '/' character.

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

Weakness information:
cwe_id: CWE-119
cwe_name: Improper Restriction of Operations within the Bounds of a Memory Buffer
description: The product performs operations on a memory buffer, but it reads from or writes to a memory location outside the buffer's intended boundary. This may result in read or write operations on unexpected memory locations that could be linked to other variables, data structures, or internal program data.
extended_description: 
            Certain languages allow direct addressing of memory locations and do not automatically ensure that these locations are valid for the memory buffer that is being referenced. This can cause read or write operations to be performed on memory locations that may be associated with other variables, data structures, or internal program data.
         
         

Fix information:
repo_url: https://gitlab.com/muttmua/mutt
commit_details: repo_url: https://gitlab.com/muttmua/mutt
author: Kevin McCarthy
author_date: 2018-07-13 11:16:33-07:00
msg: Sanitize POP bcache paths.

Protect against bcache directory path traversal for UID values.

Thanks for Jeriko One for the bug report and patch, which this commit
is based upon.
repository: repo_url: https://gitlab.com/muttmua/mutt
repo_name: visit repo url
description: visit repo url
repo_language: visit repo url
owner: muttmua
file_changes: filename: pop.c
change_type: ModificationType.MODIFY
diff: @@ -40,6 +40,25 @@
 #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];
+
+  strfcpy (clean, id, sizeof(clean));
+  mutt_sanitize_filename (clean, 1);
+
+  return clean;
+}
+
 /* write line to file */
 static int fetch_message (char *line, void *file)
 {
@@ -205,7 +224,7 @@ static int msg_cache_check (const char *id, body_cache_t *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
@@ -355,7 +374,7 @@ static int pop_fetch_headers (CONTEXT *ctx)
        *        - if we also have a body: read
        *        - if we don't have a body: new
        */
-      bcached = mutt_bcache_exists (pop_data->bcache, ctx->hdrs[i]->data) == 0;
+      bcached = mutt_bcache_exists (pop_data->bcache, cache_id (ctx->hdrs[i]->data)) == 0;
       ctx->hdrs[i]->old = 0;
       ctx->hdrs[i]->read = 0;
       if (hcached)
@@ -531,7 +550,7 @@ static int pop_fetch_message (CONTEXT* ctx, MESSAGE* msg, int msgno)
   unsigned short bcache = 1;
 
   /* see if we already have the message in body cache */
-  if ((msg->fp = mutt_bcache_get (pop_data->bcache, h->data)))
+  if ((msg->fp = mutt_bcache_get (pop_data->bcache, cache_id (h->data))))
     return 0;
 
   /*
@@ -578,7 +597,7 @@ static int pop_fetch_message (CONTEXT* ctx, MESSAGE* msg, int msgno)
 			MUTT_PROGRESS_SIZE, NetInc, h->content->length + h->content->offset - 1);
 
     /* see if we can put in body cache; use our cache as fallback */
-    if (!(msg->fp = mutt_bcache_put (pop_data->bcache, h->data, 1)))
+    if (!(msg->fp = mutt_bcache_put (pop_data->bcache, cache_id (h->data), 1)))
     {
       /* no */
       bcache = 0;
@@ -624,7 +643,7 @@ static int pop_fetch_message (CONTEXT* ctx, 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;
@@ -704,7 +723,7 @@ static int pop_sync_mailbox (CONTEXT *ctx, int *index_hint)
 	snprintf (buf, sizeof (buf), "DELE %d\r\n", ctx->hdrs[i]->refno);
 	if ((ret = pop_query (pop_data, buf, sizeof (buf))) == 0)
 	{
-	  mutt_bcache_del (pop_data->bcache, ctx->hdrs[i]->data);
+	  mutt_bcache_del (pop_data->bcache, cache_id (ctx->hdrs[i]->data));
 #if USE_HCACHE
 	  mutt_hcache_delete (hc, ctx->hdrs[i]->data, strlen);
 #endif

programming_language: C
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