CVE-2019-12210: Insufficient Information (NVD-CWE-noinfo)
value: 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.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
CVSS v3 base score: 8.1
Published: 2019-06-04T21:29Z
Repository: https://github.com/Yubico/pam-u2f
Language: C
Files changed: pam-u2f.c, util.c, util.h
Commit date: 2019-06-04 09:59:50 +0200
Commit message: 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.

Diff: diff --git a/pam-u2f.c b/pam-u2f.c
index 55d5708..071d005 100644
--- a/pam-u2f.c
+++ b/pam-u2f.c
@@ -1,7 +1,7 @@
 /*
- *  Copyright (C) 2014-2018 Yubico AB - See COPYING
+ *  Copyright (C) 2014-2019 Yubico AB - See COPYING
  */
 
 /* Define which PAM interfaces we provide */
 #define PAM_SM_AUTH
 
@@ -29,11 +29,15 @@ char *secure_getenv(const char *name) {
   return NULL;
 }
 #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;
 
   for (i = 0; i < argc; i++) {
     if (strncmp(argv[i], "max_devices=", 12) == 0)
@@ -74,18 +78,18 @@ static void parse_cfg(int flags, int argc, const char **argv, cfg_t *cfg) {
       }
       else if( strncmp (filename, "syslog", 6) == 0) {
         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;
           }
         }
       }
     }
   }
@@ -109,10 +113,16 @@ static void parse_cfg(int flags, int argc, const char **argv, cfg_t *cfg) {
     D(cfg->debug_file, "authpending_file=%s", cfg->authpending_file ? cfg->authpending_file : "(null)");
     D(cfg->debug_file, "origin=%s", cfg->origin ? cfg->origin : "(null)");
     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
 #undef DBG
 #endif
@@ -315,11 +325,12 @@ int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc,
   int authpending_file_descriptor = -1;
   if (cfg->authpending_file) {
     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));
     }
   }
@@ -383,10 +394,14 @@ done:
     DBG("alwaysok needed (otherwise return with %d)", retval);
     retval = PAM_SUCCESS;
   }
   DBG("done. [%s]", pam_strerror(pamh, retval));
 
+  if (cfg->is_custom_debug_file) {
+    fclose(cfg->debug_file);
+  }
+
   return retval;
 }
 
 PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc,
                               const char **argv) {
diff --git a/util.c b/util.c
index 6bf73b9..d6651f6 100644
--- a/util.c
+++ b/util.c
@@ -1,7 +1,7 @@
 /*
- * Copyright (C) 2014-2018 Yubico AB - See COPYING
+ * Copyright (C) 2014-2019 Yubico AB - See COPYING
  */
 
 #include "util.h"
 
 #include <u2f-server.h>
@@ -34,11 +34,11 @@ int get_devices_from_authfile(const char *authfile, const char *username,
   unsigned i, j;
 
   /* 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));
     goto err;
   }
@@ -81,10 +81,12 @@ int get_devices_from_authfile(const char *authfile, const char *username,
   opwfile = fdopen(fd, "r");
   if (opwfile == NULL) {
     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));
   if (!buf) {
     if (verbose)
@@ -209,12 +211,14 @@ out:
     buf = NULL;
   }
 
   if (opwfile)
     fclose(opwfile);
-  else if (fd >= 0)
+
+  if (fd != -1)
     close(fd);
+
   return retval;
 }
 
 void free_devices(device_t *devices, const unsigned n_devs) {
   unsigned i;
diff --git a/util.h b/util.h
index 82c3542..d2e7a4d 100644
--- a/util.h
+++ b/util.h
@@ -1,7 +1,7 @@
 /*
- * Copyright (C) 2014-2018 Yubico AB - See COPYING
+ * Copyright (C) 2014-2019 Yubico AB - See COPYING
  */
 
 #ifndef UTIL_H
 #define UTIL_H
 
@@ -43,10 +43,11 @@ typedef struct {
   const char *authpending_file;
   const char *origin;
   const char *appid;
   const char *prompt;
   FILE *debug_file;
+  int is_custom_debug_file;
 } cfg_t;
 
 typedef struct {
   unsigned char *publicKey;
   char *keyHandle;

Vulnerable code:  *  Copyright (C) 2014-2018 Yubico AB - See COPYING
        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;
            }
    authpending_file_descriptor = open(cfg->authpending_file, O_RDONLY | O_CREAT, 0664);
 * Copyright (C) 2014-2018 Yubico AB - See COPYING
  fd = open(authfile, O_RDONLY, 0);
  else if (fd >= 0)
 * Copyright (C) 2014-2018 Yubico AB - See COPYING
Fixed code:  *  Copyright (C) 2014-2019 Yubico AB - See COPYING
  struct stat st;
  FILE *file = NULL;
  int fd = -1;

        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;

  if (fd != -1)
    close(fd);

  if (file != NULL)
    fclose(file);
    authpending_file_descriptor =
      open(cfg->authpending_file, O_RDONLY | O_CREAT | O_CLOEXEC | O_NOFOLLOW | O_NOCTTY, 0664);
  if (cfg->is_custom_debug_file) {
    fclose(cfg->debug_file);
  }

 * Copyright (C) 2014-2019 Yubico AB - See COPYING
  fd = open(authfile, O_RDONLY | O_CLOEXEC | O_NOCTTY);
  } else {
    fd = -1; /* fd belongs to opwfile */

  if (fd != -1)

 * Copyright (C) 2014-2019 Yubico AB - See COPYING
  int is_custom_debug_file;