CVE-2015-5224: Insufficient Information (NVD-CWE-noinfo)
value: The mkostemp function in login-utils in util-linux when used incorrectly allows remote attackers to cause file name collision and possibly other attacks.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
CVSS v3 base score: 9.8
Published: 2017-08-23T15:29Z
Repository: https://github.com/karelzak/util-linux
Language: C
Files changed: include/fileutils.h, lib/fileutils.c, login-utils/chfn.c, login-utils/chsh.c, login-utils/setpwnam.c, login-utils/setpwnam.h, login-utils/vipw.c
Commit date: 2015-08-24 10:05:55 +0200
Commit message: chsh, chfn, vipw: fix filenames collision

The utils when compiled WITHOUT libuser then mkostemp()ing
"/etc/%s.XXXXXX" where the filename prefix is argv[0] basename.

An attacker could repeatedly execute the util with modified argv[0]
and after many many attempts mkostemp() may generate suffix which
makes sense. The result maybe temporary file with name like rc.status
ld.so.preload or krb5.keytab, etc.

Note that distros usually use libuser based ch{sh,fn} or stuff from
shadow-utils.

It's probably very minor security bug.

Addresses: CVE-2015-5224
Signed-off-by: Karel Zak <kzak@redhat.com>

Diff: diff --git a/include/fileutils.h b/include/fileutils.h
index e0e2ecee1..be6d1f7f9 100644
--- a/include/fileutils.h
+++ b/include/fileutils.h
@@ -5,18 +5,18 @@
 #include <fcntl.h>
 #include <unistd.h>
 
 #include "c.h"
 
-extern int xmkstemp(char **tmpname, char *dir);
+extern int xmkstemp(char **tmpname, const char *dir, const char *prefix);
 
-static inline FILE *xfmkstemp(char **tmpname, char *dir)
+static inline FILE *xfmkstemp(char **tmpname, const char *dir, const char *prefix)
 {
 	int fd;
 	FILE *ret;
 
-	fd = xmkstemp(tmpname, dir);
+	fd = xmkstemp(tmpname, dir, prefix);
 	if (fd == -1)
 		return NULL;
 
 	if (!(ret = fdopen(fd, "w+" UL_CLOEXECSTR))) {
 		close(fd);
diff --git a/lib/fileutils.c b/lib/fileutils.c
index 81b38ad3a..bf8e60a4b 100644
--- a/lib/fileutils.c
+++ b/lib/fileutils.c
@@ -13,31 +13,24 @@
 #include "fileutils.h"
 #include "pathnames.h"
 
 /* Create open temporary file in safe way.  Please notice that the
  * file permissions are -rw------- by default. */
-int xmkstemp(char **tmpname, char *dir)
+int xmkstemp(char **tmpname, const char *dir, const char *prefix)
 {
 	char *localtmp;
-	char *tmpenv;
+	const char *tmpenv;
 	mode_t old_mode;
 	int fd, rc;
 
 	/* Some use cases must be capable of being moved atomically
 	 * with rename(2), which is the reason why dir is here.  */
-	if (dir != NULL)
-		tmpenv = dir;
-	else
-		tmpenv = getenv("TMPDIR");
-
-	if (tmpenv)
-		rc = asprintf(&localtmp, "%s/%s.XXXXXX", tmpenv,
-			  program_invocation_short_name);
-	else
-		rc = asprintf(&localtmp, "%s/%s.XXXXXX", _PATH_TMP,
-			  program_invocation_short_name);
+	tmpenv = dir ? dir : getenv("TMPDIR");
+	if (!tmpenv)
+		tmpenv = _PATH_TMP;
 
+	rc = asprintf(&localtmp, "%s/%s.XXXXXX", tmpenv, prefix);
 	if (rc < 0)
 		return -1;
 
 	old_mode = umask(077);
 	fd = mkostemp(localtmp, O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC);
@@ -105,11 +98,11 @@ int get_fd_tabsize(void)
 #ifdef TEST_PROGRAM
 int main(void)
 {
 	FILE *f;
 	char *tmpname;
-	f = xfmkstemp(&tmpname, NULL);
+	f = xfmkstemp(&tmpname, NULL, "test");
 	unlink(tmpname);
 	free(tmpname);
 	fclose(f);
 	return EXIT_FAILURE;
 }
diff --git a/login-utils/chfn.c b/login-utils/chfn.c
index ac0a3cbde..b1c7ea25a 100644
--- a/login-utils/chfn.c
+++ b/login-utils/chfn.c
@@ -371,11 +371,11 @@ static int save_new_data(struct chfn_control *ctl)
 	if (set_value_libuser("chfn", ctl->username, ctl->pw->pw_uid,
 			LU_GECOS, gecos) < 0) {
 #else /* HAVE_LIBUSER */
 	/* write the new struct passwd to the passwd file. */
 	ctl->pw->pw_gecos = gecos;
-	if (setpwnam(ctl->pw) < 0) {
+	if (setpwnam(ctl->pw, ".chfn") < 0) {
 		warn("setpwnam failed");
 #endif
 		printf(_
 		       ("Finger information *NOT* changed.  Try again later.\n"));
 		return -1;
diff --git a/login-utils/chsh.c b/login-utils/chsh.c
index f4455d7e2..d74a1f0f1 100644
--- a/login-utils/chsh.c
+++ b/login-utils/chsh.c
@@ -321,11 +321,11 @@ int main(int argc, char **argv)
 	if (set_value_libuser("chsh", pw->pw_name, uid,
 	    LU_LOGINSHELL, info.shell) < 0)
 		errx(EXIT_FAILURE, _("Shell *NOT* changed.  Try again later."));
 #else
 	pw->pw_shell = info.shell;
-	if (setpwnam(pw) < 0)
+	if (setpwnam(pw, ".chsh") < 0)
 		err(EXIT_FAILURE, _("setpwnam failed\n"
 			"Shell *NOT* changed.  Try again later."));
 #endif
 
 	printf(_("Shell changed.\n"));
diff --git a/login-utils/setpwnam.c b/login-utils/setpwnam.c
index 79f3299d4..9f39d0181 100644
--- a/login-utils/setpwnam.c
+++ b/login-utils/setpwnam.c
@@ -69,25 +69,24 @@ static void pw_init(void);
  *  setpwnam () --
  *	takes a struct passwd in which every field is filled in and valid.
  *	If the given username exists in the passwd file, the entry is
  *	replaced with the given entry.
  */
-int setpwnam(struct passwd *pwd)
+int setpwnam(struct passwd *pwd, const char *prefix)
 {
 	FILE *fp = NULL, *pwf = NULL;
 	int save_errno;
 	int found;
 	int namelen;
 	int buflen = 256;
 	int contlen, rc;
 	char *linebuf = NULL;
 	char *tmpname = NULL;
-	char *atomic_dir = "/etc";
 
 	pw_init();
 
-	if ((fp = xfmkstemp(&tmpname, atomic_dir)) == NULL)
+	if ((fp = xfmkstemp(&tmpname, "/etc", prefix)) == NULL)
 		return -1;
 
 	/* ptmp should be owned by root.root or root.wheel */
 	if (fchown(fileno(fp), (uid_t) 0, (gid_t) 0) < 0)
 		goto fail;
diff --git a/login-utils/setpwnam.h b/login-utils/setpwnam.h
index 7d69445e8..95785923f 100644
--- a/login-utils/setpwnam.h
+++ b/login-utils/setpwnam.h
@@ -9,10 +9,12 @@
  *  This file is free software; you can redistribute it and/or
  *  modify it under the terms of the GNU Library General Public License as
  *  published by the Free Software Foundation; either version 2 of the
  *  License, or (at your option) any later version.
  */
+#ifndef UTIL_LINUX_SETPWNAM_H
+#define UTIL_LINUX_SETPWNAM_H
 
 #include "pathnames.h"
 
 #ifndef DEBUG
 # define PASSWD_FILE	_PATH_PASSWD
@@ -24,6 +26,8 @@
 # define GROUP_FILE	"/tmp/group"
 # define SHADOW_FILE	"/tmp/shadow"
 # define SGROUP_FILE	"/tmp/gshadow"
 #endif
 
-extern int setpwnam (struct passwd *pwd);
+extern int setpwnam (struct passwd *pwd, const char *prefix);
+
+#endif /* UTIL_LINUX_SETPWNAM_H */
diff --git a/login-utils/vipw.c b/login-utils/vipw.c
index 9fb25502c..a3c932f16 100644
--- a/login-utils/vipw.c
+++ b/login-utils/vipw.c
@@ -133,13 +133,12 @@ static void pw_init(void)
 
 static FILE * pw_tmpfile(int lockfd)
 {
 	FILE *fd;
 	char *tmpname = NULL;
-	char *dir = "/etc";
 
-	if ((fd = xfmkstemp(&tmpname, dir)) == NULL) {
+	if ((fd = xfmkstemp(&tmpname, "/etc", ".vipw")) == NULL) {
 		ulckpwdf();
 		err(EXIT_FAILURE, _("can't open temporary file"));
 	}
 
 	copyfile(lockfd, fileno(fd));

Vulnerable code: extern int xmkstemp(char **tmpname, char *dir);
static inline FILE *xfmkstemp(char **tmpname, char *dir)
	fd = xmkstemp(tmpname, dir);
int xmkstemp(char **tmpname, char *dir)
	char *tmpenv;
	if (dir != NULL)
		tmpenv = dir;
	else
		tmpenv = getenv("TMPDIR");

	if (tmpenv)
		rc = asprintf(&localtmp, "%s/%s.XXXXXX", tmpenv,
			  program_invocation_short_name);
	else
		rc = asprintf(&localtmp, "%s/%s.XXXXXX", _PATH_TMP,
			  program_invocation_short_name);
	f = xfmkstemp(&tmpname, NULL);
	if (setpwnam(ctl->pw) < 0) {
	if (setpwnam(pw) < 0)
int setpwnam(struct passwd *pwd)
	char *atomic_dir = "/etc";
	if ((fp = xfmkstemp(&tmpname, atomic_dir)) == NULL)
extern int setpwnam (struct passwd *pwd);
	char *dir = "/etc";
	if ((fd = xfmkstemp(&tmpname, dir)) == NULL) {
Fixed code: extern int xmkstemp(char **tmpname, const char *dir, const char *prefix);
static inline FILE *xfmkstemp(char **tmpname, const char *dir, const char *prefix)
	fd = xmkstemp(tmpname, dir, prefix);
int xmkstemp(char **tmpname, const char *dir, const char *prefix)
	const char *tmpenv;
	tmpenv = dir ? dir : getenv("TMPDIR");
	if (!tmpenv)
		tmpenv = _PATH_TMP;
	rc = asprintf(&localtmp, "%s/%s.XXXXXX", tmpenv, prefix);
	f = xfmkstemp(&tmpname, NULL, "test");
	if (setpwnam(ctl->pw, ".chfn") < 0) {
	if (setpwnam(pw, ".chsh") < 0)
int setpwnam(struct passwd *pwd, const char *prefix)
	if ((fp = xfmkstemp(&tmpname, "/etc", prefix)) == NULL)
#ifndef UTIL_LINUX_SETPWNAM_H
#define UTIL_LINUX_SETPWNAM_H
extern int setpwnam (struct passwd *pwd, const char *prefix);

#endif /* UTIL_LINUX_SETPWNAM_H */
	if ((fd = xfmkstemp(&tmpname, "/etc", ".vipw")) == NULL) {