CVE-2019-12210 (published 2019-06-04T21:29Z)

In Yubico pam-u2f 1.0.7, when configured with debug and a custom debug log file is set using debug_file, that file descriptor is not closed when a new process is spawned. This leads to the file descriptor being inherited into the child process; the child process can then read from and write to it. This can leak sensitive information and also, if written to, be used to fill the disk or plant misinformation.

Severity: MEDIUM
CVSS v2 base score: 5.5 (AV:N/AC:L/Au:S/C:P/I:P/A:N)
CVSS v3 base score: 8.1, HIGH (CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N)

Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/Yubico/pam-u2f
commit_details: repo_url: https://github.com/Yubico/pam-u2f
author: Gabriel Kihlman
author_date: 2019-06-04 09:59:50+02:00
msg: Do not leak file descriptor when doing exec

When opening a custom debug file, the descriptor would stay
open when calling exec and leak to the child process.

Make sure all files are opened with close-on-exec.

This fixes CVE-2019-12210.

Thanks to Matthias Gerstner of the SUSE Security Team for reporting
the issue.
repository: repo_url: https://github.com/Yubico/pam-u2f
repo_name: Yubico/pam-u2f
description: Pluggable Authentication Module (PAM) for U2F and FIDO2
repo_language: C
owner: Yubico
file_changes: filename: pam-u2f.c
change_type: ModificationType.MODIFY
diff: @@ -1,5 +1,5 @@
 /*
- *  Copyright (C) 2014-2018 Yubico AB - See COPYING
+ *  Copyright (C) 2014-2019 Yubico AB - See COPYING
  */
 
 /* Define which PAM interfaces we provide */
@@ -31,7 +31,11 @@ char *secure_getenv(const char *name) {
 #endif
 
 static void parse_cfg(int flags, int argc, const char **argv, cfg_t *cfg) {
+  struct stat st;
+  FILE *file = NULL;
+  int fd = -1;
   int i;
+
   memset(cfg, 0, sizeof(cfg_t));
   cfg->debug_file = stderr;
 
@@ -76,14 +80,14 @@ static void parse_cfg(int flags, int argc, const char **argv, cfg_t *cfg) {
         cfg->debug_file = (FILE *)-1;
       }
       else {
-        struct stat st;
-        FILE *file;
-        if(lstat(filename, &st) == 0) {
-          if(S_ISREG(st.st_mode)) {
-            file = fopen(filename, "a");
-            if(file != NULL) {
-              cfg->debug_file = file;
-            }
+        fd = open(filename, O_WRONLY | O_APPEND | O_CLOEXEC | O_NOFOLLOW | O_NOCTTY);
+        if (fd >= 0 && (fstat(fd, &st) == 0) && S_ISREG(st.st_mode)) {
+          file = fdopen(fd, "a");
+          if(file != NULL) {
+            cfg->debug_file = file;
+            cfg->is_custom_debug_file = 1;
+            file = NULL;
+            fd = -1;
           }
         }
       }
@@ -111,6 +115,12 @@ static void parse_cfg(int flags, int argc, const char **argv, cfg_t *cfg) {
     D(cfg->debug_file, "appid=%s", cfg->appid ? cfg->appid : "(null)");
     D(cfg->debug_file, "prompt=%s", cfg->prompt ? cfg->prompt : "(null)");
   }
+
+  if (fd != -1)
+    close(fd);
+
+  if (file != NULL)
+    fclose(file);
 }
 
 #ifdef DBG
@@ -317,7 +327,8 @@ int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc,
     DBG("Using file '%s' for emitting touch request notifications", cfg->authpending_file);
 
     // Open (or create) the authpending_file to indicate that we start waiting for a touch
-    authpending_file_descriptor = open(cfg->authpending_file, O_RDONLY | O_CREAT, 0664);
+    authpending_file_descriptor =
+      open(cfg->authpending_file, O_RDONLY | O_CREAT | O_CLOEXEC | O_NOFOLLOW | O_NOCTTY, 0664);
     if (authpending_file_descriptor < 0) {
       DBG("Unable to emit 'authentication started' notification by opening the file '%s', (%s)",
           cfg->authpending_file, strerror(errno));
@@ -385,6 +396,10 @@ done:
   }
   DBG("done. [%s]", pam_strerror(pamh, retval));
 
+  if (cfg->is_custom_debug_file) {
+    fclose(cfg->debug_file);
+  }
+
   return retval;
 }
 

programming_language: C
filename: util.c
change_type: ModificationType.MODIFY
diff: @@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014-2018 Yubico AB - See COPYING
+ * Copyright (C) 2014-2019 Yubico AB - See COPYING
  */
 
 #include "util.h"
@@ -36,7 +36,7 @@ int get_devices_from_authfile(const char *authfile, const char *username,
   /* Ensure we never return uninitialized count. */
   *n_devs = 0;
 
-  fd = open(authfile, O_RDONLY, 0);
+  fd = open(authfile, O_RDONLY | O_CLOEXEC | O_NOCTTY);
   if (fd < 0) {
     if (verbose)
       D(debug_file, "Cannot open file: %s (%s)", authfile, strerror(errno));
@@ -83,6 +83,8 @@ int get_devices_from_authfile(const char *authfile, const char *username,
     if (verbose)
       D(debug_file, "fdopen: %s", strerror(errno));
     goto err;
+  } else {
+    fd = -1; /* fd belongs to opwfile */
   }
 
   buf = malloc(sizeof(char) * (DEVSIZE * max_devs));
@@ -211,8 +213,10 @@ out:
 
   if (opwfile)
     fclose(opwfile);
-  else if (fd >= 0)
+
+  if (fd != -1)
     close(fd);
+
   return retval;
 }
 

programming_language: C
filename: util.h
change_type: ModificationType.MODIFY
diff: @@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014-2018 Yubico AB - See COPYING
+ * Copyright (C) 2014-2019 Yubico AB - See COPYING
  */
 
 #ifndef UTIL_H
@@ -45,6 +45,7 @@ typedef struct {
   const char *appid;
   const char *prompt;
   FILE *debug_file;
+  int is_custom_debug_file;
 } cfg_t;
 
 typedef struct {

programming_language: C