CVE-2019-12209: Improper Link Resolution Before File Access ('Link Following') (CWE-59)
value: Yubico pam-u2f 1.0.7 attempts parsing of the configured authfile (default $HOME/.config/Yubico/u2f_keys) as root (unless openasuser was enabled), and does not properly verify that the path lacks symlinks pointing to other files on the system owned by root. If the debug option is enabled in the PAM configuration, part of the file contents of a symlink target will be logged, possibly revealing sensitive information.
CWE description: The product attempts to access a file based on the filename, but it does not properly prevent that filename from identifying a link or shortcut that resolves to an unintended resource.
CVSS v3 base score: 7.5
Published: 2019-06-04T21:29Z
Repository: https://github.com/Yubico/pam-u2f
Language: C
Files changed: Makefile.am, README, configure.ac, drop_privs.c, drop_privs.h, pam-u2f.c
Commit date: 2019-06-04 10:06:41 +0200
Commit message: Drop privileges by default when opening user-related files

The module is typically executed as root and would sometimes
open files or follow symlinks that could be controlled from the
outside.

Drop privileges to the target user before opening any files.

Fixes CVE-2019-12209.

Thanks to Matthias Gerstner of the SUSE Security Team for reporting
the issue.

Diff: diff --git a/Makefile.am b/Makefile.am
index c0d2d1e..d761939 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -11,10 +11,11 @@ libdir = $(PAMDIR)
 
 lib_LTLIBRARIES = pam_u2f.la
 
 pam_u2f_la_SOURCES = pam-u2f.c
 pam_u2f_la_SOURCES += util.c util.h
+pam_u2f_la_SOURCES += drop_privs.h drop_privs.c
 
 pam_u2f_la_LIBADD = -lpam
 pam_u2f_la_LIBADD += $(LIBU2FHOST_LIBS) $(LIBU2FSERVER_LIBS)
 pam_u2f_la_LDFLAGS = -module -avoid-version
 
diff --git a/README b/README
index a6247a3..04f9541 100644
--- a/README
+++ b/README
@@ -112,10 +112,12 @@ authenticate is not found inside authfile or if authfile is missing/malformed.
 
 openasuser::
 Setuid to the authenticating user when opening the authfile. Useful when the
 user's home is stored on an NFS volume mounted with the root_squash option
 (which maps root to nobody which will not be able to read the file).
+Note that after release 1.0.8 this is done by default when no global
+authfile or XDG_CONFIG_HOME environment variable has been set.
 
 alwaysok::
 Set to enable all authentication attempts to succeed (aka presentation mode).
 
 max_devices=n_devices::
@@ -162,10 +164,15 @@ individually, where users can create the mapping in their home directories.
 If the central authorization mapping file is being used, user home directory
 mappings will not be used and the opposite applies if user home directory
 mappings are being used, the central authorization mappings file will not
 be used.
 
+By default the mapping file inside a home directory will be opened as
+the target user, whereas the central file will be opened as `root`. If
+the `XDG_CONFIG_HOME` variable is set, privileges will not be dropped
+unless the `openasuser` configuration setting is set.
+
 IMPORTANT: Using pam-u2f to secure the login to a computer while
 storing the mapping file in an encrypted home directory, will result
 in the impossibility of logging into the system. The partition is
 decrypted after login and the mapping file can not be accessed.
 
@@ -182,10 +189,14 @@ The mappings should look like this, one per line:
 Now add `authfile=/etc/u2f_mappings` to your PAM configuration line, so it
 looks like:
 
  auth sufficient pam_u2f.so authfile=/etc/u2f_mappings
 
+If you do not set the `openasuser` setting, the authfile will be opened
+and parsed as `root` so make sure it has the correct owner and
+permissions set.
+
 IMPORTANT: On dynamics networks (e.g. where hostnames are set by DHCP),
 users should not rely on the default origin and appid ("pam://$HOSTNAME")
 but set those parameters explicitly to the same value.
 
 == Individual authorization mapping by user
@@ -195,10 +206,17 @@ line:
 
  <username>:<KeyHandle1>,<UserKey1>:<KeyHandle2>,<UserKey2>:...
 
 This is much the same concept as the SSH authorized_keys file.
 
+In this case, pam-u2f will drop privileges and read the mapping file
+as that user. This happens regardless of the `openasuser` option being
+set.
+
+Note that if you set the XDG_CONFIG_HOME variable, privileges will not
+be dropped by default. Consider also setting `openasuser` in that case.
+
 [[registration]]
 Obtaining key-handles and public keys
 -------------------------------------
 
 In order to obtain the required information for the authentication procedure,
diff --git a/configure.ac b/configure.ac
index 58fedcc..1ec2566 100644
--- a/configure.ac
+++ b/configure.ac
@@ -35,10 +35,12 @@ AC_CHECK_HEADERS([security/pam_appl.h], [],
 AC_CHECK_HEADERS([security/pam_modules.h security/_pam_macros.h security/pam_modutil.h], [], [],
   [#include <sys/types.h>
    #include <security/pam_appl.h>])
 AC_CHECK_LIB([pam], [pam_start])
 
+AC_SEARCH_LIBS([pam_modutil_drop_priv], ["pam"], [AC_DEFINE([HAVE_PAM_MODUTIL_DROP_PRIV], [1])])
+
 case "$host" in
      *darwin*)  PAMDIR="/usr/lib/pam";;
      *linux*)   PAMDIR="/lib/x86_64-linux-gnu/security";;
      *)         PAMDIR="/usr/lib"
 esac
@@ -69,10 +71,12 @@ LIBS=$am_save_LIBS
 
 AC_ARG_VAR([CWFLAGS], [Warning flags])
 AX_CHECK_COMPILE_FLAG([-Wall], [CWFLAGS="-Wall"])
 AX_CHECK_COMPILE_FLAG([-Wextra], [CWFLAGS="$CWFLAGS -Wextra"])
 AX_CHECK_COMPILE_FLAG([-Wconversion], [CWFLAGS="$CWFLAGS -Wconversion"])
+# Because pam headers are doing sign-conversion, see PAM_MODUTIL_DEF_PRIVS in pam_modutil.h
+AX_CHECK_COMPILE_FLAG([-Wconversion], [CWFLAGS="$CWFLAGS -Wno-sign-conversion"])
 AX_CHECK_COMPILE_FLAG([-Wpedantic], [CWFLAGS="$CWFLAGS -Wpedantic"])
 AX_CHECK_COMPILE_FLAG([-Wformat=2], [CWFLAGS="$CWFLAGS -Wformat=2"])
 AX_CHECK_COMPILE_FLAG([-Wstrict-prototypes], [CWFLAGS="$CWFLAGS -Wstrict-prototypes"])
 AX_CHECK_COMPILE_FLAG([-Wmissing-declarations], [CWFLAGS="$CWFLAGS -Wmissing-declarations"])
 AX_CHECK_COMPILE_FLAG([-Wmissing-prototypes], [CWFLAGS="$CWFLAGS -Wmissing-prototypes"])
diff --git a/drop_privs.c b/drop_privs.c
new file mode 100644
index 0000000..7fe35c5
--- /dev/null
+++ b/drop_privs.c
@@ -0,0 +1,129 @@
+/* Written by Ricky Zhou <ricky@fedoraproject.org>
+ * Fredrik Thulin <fredrik@yubico.com> implemented pam_modutil_drop_priv
+ *
+ * Copyright (c) 2011-2014 Yubico AB
+ * Copyright (c) 2011 Ricky Zhou <ricky@fedoraproject.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *
+ *     * Redistributions in binary form must reproduce the above
+ *       copyright notice, this list of conditions and the following
+ *       disclaimer in the documentation and/or other materials provided
+ *       with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef HAVE_PAM_MODUTIL_DROP_PRIV
+
+#include <unistd.h>
+#include <pwd.h>
+#include <grp.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "drop_privs.h"
+#include "util.h"
+
+#ifdef HAVE_SECURITY_PAM_APPL_H
+#include <security/pam_appl.h>
+#endif
+#ifdef HAVE_SECURITY_PAM_MODULES_H
+#include <security/pam_modules.h>
+#endif
+
+
+int pam_modutil_drop_priv(pam_handle_t *pamh, struct _ykpam_privs *privs, struct passwd *pw) {
+    privs->saved_euid = geteuid();
+    privs->saved_egid = getegid();
+
+    if ((privs->saved_euid == pw->pw_uid) && (privs->saved_egid == pw->pw_gid)) {
+        D (privs->debug_file, "Privilges already dropped, pretend it is all right");
+        return 0;
+    }
+
+    privs->saved_groups_length = getgroups(0, NULL);
+    if (privs->saved_groups_length < 0) {
+        D (privs->debug_file, "getgroups: %s", strerror(errno));
+        return -1;
+    }
+
+    if (privs->saved_groups_length > SAVED_GROUPS_MAX_LEN) {
+        D (privs->debug_file, "too many groups, limiting.");
+        privs->saved_groups_length = SAVED_GROUPS_MAX_LEN;
+    }
+
+    if (privs->saved_groups_length > 0) {
+        if (getgroups(privs->saved_groups_length, privs->saved_groups) < 0) {
+            D (privs->debug_file, "getgroups: %s", strerror(errno));
+            goto free_out;
+        }
+    }
+
+    if (initgroups(pw->pw_name, pw->pw_gid) < 0) {
+        D (privs->debug_file, "initgroups: %s", strerror(errno));
+        goto free_out;
+    }
+
+    if (setegid(pw->pw_gid) < 0) {
+        D (privs->debug_file, "setegid: %s", strerror(errno));
+        goto free_out;
+    }
+
+    if (seteuid(pw->pw_uid) < 0) {
+        D (privs->debug_file, "seteuid: %s", strerror(errno));
+        goto free_out;
+    }
+
+    return 0;
+free_out:
+    return -1;
+}
+
+int pam_modutil_regain_priv(pam_handle_t *pamh, struct _ykpam_privs *privs) {
+    if ((privs->saved_euid == geteuid()) && (privs->saved_egid == getegid())) {
+        D (privs->debug_file, "Privilges already as requested, pretend it is all right");
+        return 0;
+    }
+
+    if (seteuid(privs->saved_euid) < 0) {
+        D (privs->debug_file, "seteuid: %s", strerror(errno));
+        return -1;
+    }
+
+    if (setegid(privs->saved_egid) < 0) {
+        D (privs->debug_file, "setegid: %s", strerror(errno));
+        return -1;
+    }
+
+    if (setgroups(privs->saved_groups_length, privs->saved_groups) < 0) {
+        D (privs->debug_file, "setgroups: %s", strerror(errno));
+        return -1;
+    }
+
+    return 0;
+}
+
+#else
+
+// drop_privs.c:124: warning: ISO C forbids an empty translation unit [-Wpedantic]
+typedef int make_iso_compilers_happy;
+
+#endif // HAVE_PAM_MODUTIL_DROP_PRIV
diff --git a/drop_privs.h b/drop_privs.h
new file mode 100644
index 0000000..2d25a19
--- /dev/null
+++ b/drop_privs.h
@@ -0,0 +1,64 @@
+/* Copyright (c) 2011-2014 Yubico AB
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *
+ *     * Redistributions in binary form must reproduce the above
+ *       copyright notice, this list of conditions and the following
+ *       disclaimer in the documentation and/or other materials provided
+ *       with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __PAM_U2F_DROP_PRIVS_H_INCLUDED__
+#define __PAM_U2F_DROP_PRIVS_H_INCLUDED__
+
+#ifdef HAVE_PAM_MODUTIL_DROP_PRIV
+#include <security/pam_modutil.h>
+#else
+
+#include <pwd.h>
+#include <stdio.h>
+
+#ifdef HAVE_SECURITY_PAM_APPL_H
+#include <security/pam_appl.h>
+#endif
+#ifdef HAVE_SECURITY_PAM_MODULES_H
+#include <security/pam_modules.h>
+#endif
+
+#define SAVED_GROUPS_MAX_LEN 64 /* as pam_modutil.. */
+
+struct _ykpam_privs {
+  uid_t saved_euid;
+  gid_t saved_egid;
+  gid_t *saved_groups;
+  int saved_groups_length;
+  FILE *debug_file;
+};
+
+#define PAM_MODUTIL_DEF_PRIVS(n) \
+  gid_t n##_saved_groups[SAVED_GROUPS_MAX_LEN]; \
+  struct _ykpam_privs n = {-1, -1, n##_saved_groups, SAVED_GROUPS_MAX_LEN, cfg->debug_file}
+
+int pam_modutil_drop_priv(pam_handle_t *, struct _ykpam_privs *, struct passwd *);
+int pam_modutil_regain_priv(pam_handle_t *, struct _ykpam_privs *);
+
+#endif
+#endif
diff --git a/pam-u2f.c b/pam-u2f.c
index 071d005..b01697c 100644
--- a/pam-u2f.c
+++ b/pam-u2f.c
@@ -18,10 +18,11 @@
 #include <pwd.h>
 #include <string.h>
 #include <errno.h>
 
 #include "util.h"
+#include "drop_privs.h"
 
 /* If secure_getenv is not defined, define it here */
 #ifndef HAVE_SECURE_GETENV
 char *secure_getenv(const char *);
 char *secure_getenv(const char *name) {
@@ -146,15 +147,16 @@ int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc,
   size_t authfile_dir_len;
   int pgu_ret, gpn_ret;
   int retval = PAM_IGNORE;
   device_t *devices = NULL;
   unsigned n_devices = 0;
-  int openasuser;
+  int openasuser = 0;
   int should_free_origin = 0;
   int should_free_appid = 0;
   int should_free_auth_file = 0;
   int should_free_authpending_file = 0;
+  PAM_MODUTIL_DEF_PRIVS(privs);
 
   parse_cfg(flags, argc, argv, cfg);
 
   if (!cfg->origin) {
     strcpy(buffer, DEFAULT_ORIGIN_PREFIX);
@@ -233,10 +235,13 @@ int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc,
         DBG("Unable to allocate memory");
         retval = PAM_IGNORE;
         goto done;
       }
 
+      /* Opening a file in a users $HOME, need to drop privs for security */
+      openasuser = geteuid() == 0 ? 1 : 0;
+
       snprintf(buf, authfile_dir_len,
                "%s/.config%s", pw->pw_dir, DEFAULT_AUTHFILE);
     } else {
       DBG("Variable %s set to %s", DEFAULT_AUTHFILE_DIR_VAR, authfile_dir);
       authfile_dir_len = strlen(authfile_dir) + strlen(DEFAULT_AUTHFILE) + 1;
@@ -248,40 +253,48 @@ int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc,
         goto done;
       }
 
       snprintf(buf, authfile_dir_len,
                "%s%s", authfile_dir, DEFAULT_AUTHFILE);
+
+      if (!openasuser) {
+	DBG("WARNING: not dropping privileges when reading %s, please "
+	    "consider setting openasuser=1 in the module configuration", buf);
+      }
     }
 
-    DBG("Using default authentication file %s", buf);
+    DBG("Using authentication file %s", buf);
 
     cfg->auth_file = buf; /* cfg takes ownership */
     should_free_auth_file = 1;
     buf = NULL;
   } else {
     DBG("Using authentication file %s", cfg->auth_file);
   }
 
-  openasuser = geteuid() == 0 && cfg->openasuser;
+  if (!openasuser) {
+    openasuser = geteuid() == 0 && cfg->openasuser;
+  }
   if (openasuser) {
-    if (seteuid(pw_s.pw_uid)) {
-      DBG("Unable to switch user to uid %i", pw_s.pw_uid);
+    DBG("Dropping privileges");
+    if (pam_modutil_drop_priv(pamh, &privs, pw)) {
+      DBG("Unable to switch user to uid %i", pw->pw_uid);
       retval = PAM_IGNORE;
       goto done;
     }
-    DBG("Switched to uid %i", pw_s.pw_uid);
+    DBG("Switched to uid %i", pw->pw_uid);
   }
   retval = get_devices_from_authfile(cfg->auth_file, user, cfg->max_devs,
                                      cfg->debug, cfg->debug_file,
                                      devices, &n_devices);
   if (openasuser) {
-    if (seteuid(0)) {
-      DBG("Unable to switch back to uid 0");
+    if (pam_modutil_regain_priv(pamh, &privs)) {
+      DBG("could not restore privileges");
       retval = PAM_IGNORE;
       goto done;
     }
-    DBG("Switched back to uid 0");
+    DBG("Restored privileges");
   }
 
   if (retval != 1) {
     // for nouserok; make sure errors in get_devices_from_authfile don't
     // result in valid devices

Vulnerable code:   int openasuser;
    DBG("Using default authentication file %s", buf);
  openasuser = geteuid() == 0 && cfg->openasuser;
    if (seteuid(pw_s.pw_uid)) {
      DBG("Unable to switch user to uid %i", pw_s.pw_uid);
    DBG("Switched to uid %i", pw_s.pw_uid);
    if (seteuid(0)) {
      DBG("Unable to switch back to uid 0");
    DBG("Switched back to uid 0");
Fixed code: pam_u2f_la_SOURCES += drop_privs.h drop_privs.c
Note that after release 1.0.8 this is done by default when no global
authfile or XDG_CONFIG_HOME environment variable has been set.
By default the mapping file inside a home directory will be opened as
the target user, whereas the central file will be opened as `root`. If
the `XDG_CONFIG_HOME` variable is set, privileges will not be dropped
unless the `openasuser` configuration setting is set.

If you do not set the `openasuser` setting, the authfile will be opened
and parsed as `root` so make sure it has the correct owner and
permissions set.

In this case, pam-u2f will drop privileges and read the mapping file
as that user. This happens regardless of the `openasuser` option being
set.

Note that if you set the XDG_CONFIG_HOME variable, privileges will not
be dropped by default. Consider also setting `openasuser` in that case.

AC_SEARCH_LIBS([pam_modutil_drop_priv], ["pam"], [AC_DEFINE([HAVE_PAM_MODUTIL_DROP_PRIV], [1])])

# Because pam headers are doing sign-conversion, see PAM_MODUTIL_DEF_PRIVS in pam_modutil.h
AX_CHECK_COMPILE_FLAG([-Wconversion], [CWFLAGS="$CWFLAGS -Wno-sign-conversion"])
/* Written by Ricky Zhou <ricky@fedoraproject.org>
 * Fredrik Thulin <fredrik@yubico.com> implemented pam_modutil_drop_priv
 *
 * Copyright (c) 2011-2014 Yubico AB
 * Copyright (c) 2011 Ricky Zhou <ricky@fedoraproject.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *
 *     * Redistributions in binary form must reproduce the above
 *       copyright notice, this list of conditions and the following
 *       disclaimer in the documentation and/or other materials provided
 *       with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef HAVE_PAM_MODUTIL_DROP_PRIV

#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>

#include "drop_privs.h"
#include "util.h"

#ifdef HAVE_SECURITY_PAM_APPL_H
#include <security/pam_appl.h>
#endif
#ifdef HAVE_SECURITY_PAM_MODULES_H
#include <security/pam_modules.h>
#endif


int pam_modutil_drop_priv(pam_handle_t *pamh, struct _ykpam_privs *privs, struct passwd *pw) {
    privs->saved_euid = geteuid();
    privs->saved_egid = getegid();

    if ((privs->saved_euid == pw->pw_uid) && (privs->saved_egid == pw->pw_gid)) {
        D (privs->debug_file, "Privilges already dropped, pretend it is all right");
        return 0;
    }

    privs->saved_groups_length = getgroups(0, NULL);
    if (privs->saved_groups_length < 0) {
        D (privs->debug_file, "getgroups: %s", strerror(errno));
        return -1;
    }

    if (privs->saved_groups_length > SAVED_GROUPS_MAX_LEN) {
        D (privs->debug_file, "too many groups, limiting.");
        privs->saved_groups_length = SAVED_GROUPS_MAX_LEN;
    }

    if (privs->saved_groups_length > 0) {
        if (getgroups(privs->saved_groups_length, privs->saved_groups) < 0) {
            D (privs->debug_file, "getgroups: %s", strerror(errno));
            goto free_out;
        }
    }

    if (initgroups(pw->pw_name, pw->pw_gid) < 0) {
        D (privs->debug_file, "initgroups: %s", strerror(errno));
        goto free_out;
    }

    if (setegid(pw->pw_gid) < 0) {
        D (privs->debug_file, "setegid: %s", strerror(errno));
        goto free_out;
    }

    if (seteuid(pw->pw_uid) < 0) {
        D (privs->debug_file, "seteuid: %s", strerror(errno));
        goto free_out;
    }

    return 0;
free_out:
    return -1;
}

int pam_modutil_regain_priv(pam_handle_t *pamh, struct _ykpam_privs *privs) {
    if ((privs->saved_euid == geteuid()) && (privs->saved_egid == getegid())) {
        D (privs->debug_file, "Privilges already as requested, pretend it is all right");
        return 0;
    }

    if (seteuid(privs->saved_euid) < 0) {
        D (privs->debug_file, "seteuid: %s", strerror(errno));
        return -1;
    }

    if (setegid(privs->saved_egid) < 0) {
        D (privs->debug_file, "setegid: %s", strerror(errno));
        return -1;
    }

    if (setgroups(privs->saved_groups_length, privs->saved_groups) < 0) {
        D (privs->debug_file, "setgroups: %s", strerror(errno));
        return -1;
    }

    return 0;
}

#else

// drop_privs.c:124: warning: ISO C forbids an empty translation unit [-Wpedantic]
typedef int make_iso_compilers_happy;

#endif // HAVE_PAM_MODUTIL_DROP_PRIV
/* Copyright (c) 2011-2014 Yubico AB
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *
 *     * Redistributions in binary form must reproduce the above
 *       copyright notice, this list of conditions and the following
 *       disclaimer in the documentation and/or other materials provided
 *       with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef __PAM_U2F_DROP_PRIVS_H_INCLUDED__
#define __PAM_U2F_DROP_PRIVS_H_INCLUDED__

#ifdef HAVE_PAM_MODUTIL_DROP_PRIV
#include <security/pam_modutil.h>
#else

#include <pwd.h>
#include <stdio.h>

#ifdef HAVE_SECURITY_PAM_APPL_H
#include <security/pam_appl.h>
#endif
#ifdef HAVE_SECURITY_PAM_MODULES_H
#include <security/pam_modules.h>
#endif

#define SAVED_GROUPS_MAX_LEN 64 /* as pam_modutil.. */

struct _ykpam_privs {
  uid_t saved_euid;
  gid_t saved_egid;
  gid_t *saved_groups;
  int saved_groups_length;
  FILE *debug_file;
};

#define PAM_MODUTIL_DEF_PRIVS(n) \
  gid_t n##_saved_groups[SAVED_GROUPS_MAX_LEN]; \
  struct _ykpam_privs n = {-1, -1, n##_saved_groups, SAVED_GROUPS_MAX_LEN, cfg->debug_file}

int pam_modutil_drop_priv(pam_handle_t *, struct _ykpam_privs *, struct passwd *);
int pam_modutil_regain_priv(pam_handle_t *, struct _ykpam_privs *);

#endif
#endif
#include "drop_privs.h"
  int openasuser = 0;
  PAM_MODUTIL_DEF_PRIVS(privs);
      /* Opening a file in a users $HOME, need to drop privs for security */
      openasuser = geteuid() == 0 ? 1 : 0;


      if (!openasuser) {
	DBG("WARNING: not dropping privileges when reading %s, please "
	    "consider setting openasuser=1 in the module configuration", buf);
      }
    DBG("Using authentication file %s", buf);
  if (!openasuser) {
    openasuser = geteuid() == 0 && cfg->openasuser;
  }
    DBG("Dropping privileges");
    if (pam_modutil_drop_priv(pamh, &privs, pw)) {
      DBG("Unable to switch user to uid %i", pw->pw_uid);
    DBG("Switched to uid %i", pw->pw_uid);
    if (pam_modutil_regain_priv(pamh, &privs)) {
      DBG("could not restore privileges");
    DBG("Restored privileges");