[PATCH] openssh - loginrec.c - Non-atomic file operations.

Robin Hack rhack at redhat.com
Thu May 22 23:36:41 EST 2014


Hi all.

I rewrited lastlog_openseek function. Now is little more atomic when
file operations on lastlog file happens.

For more details why separate stat and open isn't so safe please take a
look at: http://www.akkadia.org/drepper/defprogramming.pdf


Have nice day
Robin Hack
-------------- next part --------------
diff --git a/loginrec.c b/loginrec.c
index 4219b9a..281d650 100644
--- a/loginrec.c
+++ b/loginrec.c
@@ -163,6 +163,7 @@
 #include <string.h>
 #include <time.h>
 #include <unistd.h>
+#include <dirent.h>
 
 #include "xmalloc.h"
 #include "key.h"
@@ -1476,41 +1477,65 @@ lastlog_openseek(struct logininfo *li, int *fd, int filemode)
 	off_t offset;
 	char lastlog_file[1024];
 	struct stat st;
-
-	if (stat(LASTLOG_FILE, &st) != 0) {
-		logit("%s: Couldn't stat %s: %s", __func__,
-		    LASTLOG_FILE, strerror(errno));
-		return (0);
-	}
-	if (S_ISDIR(st.st_mode)) {
-		snprintf(lastlog_file, sizeof(lastlog_file), "%s/%s",
-		    LASTLOG_FILE, li->username);
-	} else if (S_ISREG(st.st_mode)) {
+	DIR *lastlog_dir;
+	int dir_fd = -1;
+
+	/* Try to open directory */
+	lastlog_dir = opendir(lastlog_file);
+	if (lastlog_dir != NULL) {
+		/* So. We are directory. */
+		dir_fd = dirfd(lastlog_dir);
+		snprintf(lastlog_file, sizeof(lastlog_file), "/dev/fd/%d/%s",
+			dir_fd, li->username);
+	} else if ((errno == ENOTDIR) || (errno == ENOENT)) {
+		/* Not directory. Try to open as file. */
 		strlcpy(lastlog_file, LASTLOG_FILE, sizeof(lastlog_file));
+	} else if (errno == EACCES) {
+		logit("%s: %.100s permission denied!", __func__,
+			LASTLOG_FILE);
+		return (0);
 	} else {
-		logit("%s: %.100s is not a file or directory!", __func__,
-		    LASTLOG_FILE);
+		/* Few errnos left. Most of then should happen
+		   because kernel limits... */
+		logit("%s: %.100s is not accessible!", __func__,
+		LASTLOG_FILE);
 		return (0);
 	}
 
+	/* Here is place for possible race condition. */
 	*fd = open(lastlog_file, filemode, 0600);
+	if (dir_fd != -1) {
+        closedir(lastlog_dir);
+	}
+
 	if (*fd < 0) {
 		debug("%s: Couldn't open %s: %s", __func__,
-		    lastlog_file, strerror(errno));
+			lastlog_file, strerror(errno));
 		return (0);
 	}
 
-	if (S_ISREG(st.st_mode)) {
-		/* find this uid's offset in the lastlog file */
-		offset = (off_t) ((u_long)li->uid * sizeof(struct lastlog));
+	if (fstat(*fd, &st) != 0) {
+		close(*fd);
+		logit("%s: Couldn't stat %s: %s", __func__,
+			LASTLOG_FILE, strerror(errno));
+		return (0);
+	}
 
-		if (lseek(*fd, offset, SEEK_SET) != offset) {
-			logit("%s: %s->lseek(): %s", __func__,
-			    lastlog_file, strerror(errno));
-			close(*fd);
-			return (0);
-		}
+	if (!S_ISREG(st.st_mode)) {
+		close(*fd);
+		logit("%s: %.100s is not a regular file!", __func__,
+			LASTLOG_FILE);
+		return (0);
 	}
+	/* find this uid's offset in the lastlog file */
+    offset = (off_t) ((u_long)li->uid * sizeof(struct lastlog));
+
+    if (lseek(*fd, offset, SEEK_SET) != offset) {
+        logit("%s: %s->lseek(): %s", __func__,
+            lastlog_file, strerror(errno));
+        close(*fd);
+        return (0);
+    }
 
 	return (1);
 }


More information about the openssh-unix-dev mailing list