Author: Stéphane Graber <stgraber@ubuntu.com>
Description: pam_loginuid: Ignore failure in user namespaces
    When running pam_loginuid in a container using the user namespaces, even
    uid 0 isn't allowed to set the loginuid property.
 .
    This change catches the EACCES from opening loginuid, checks if the user
    is in the host namespace (by comparing the uid_map with the host's one)
    and only if that's the case, sets rc to 1.
 .
    Should uid_map not exist or be unreadable for some reason, it'll be
    assumed that the process is running on the host's namespace.
 .
    The initial reason behind this change was failure to ssh into an
    unprivileged container (using a 3.13 kernel and current LXC) when using
    a standard pam profile for sshd (which requires success from
    pam_loginuid).
 .
    I believe this solution doesn't have any drawback and will allow people
    to use unprivileged containers normally. An alternative would be to have
    all distros set pam_loginuid as optional but that'd be bad for any of
    the other potential failure case which people may care about.
 .
    There has also been some discussions to get some of the audit features
    tied with the user namespaces but currently none of that has been merged
    upstream and the currently proposed implementation doesn't cover
    loginuid (nor is it clear how this should even work when loginuid is set
    as immutable after initial write).
 .
    Signed-off-by: Steve Langasek <vorlon@debian.org>
    Signed-off-by: Dmitry V. Levin <ldv@altlinux.org>

Index: ubuntu/modules/pam_loginuid/pam_loginuid.c
===================================================================
--- ubuntu.orig/modules/pam_loginuid/pam_loginuid.c	2014-01-31 21:07:08.665185675 +0000
+++ ubuntu/modules/pam_loginuid/pam_loginuid.c	2014-01-31 21:05:05.000000000 +0000
@@ -47,25 +47,56 @@
 
 /*
  * This function writes the loginuid to the /proc system. It returns
- * 0 on success and 1 on failure.
+ * PAM_SUCCESS on success,
+ * PAM_IGNORE when /proc/self/loginuid does not exist,
+ * PAM_SESSION_ERR in case of any other error.
  */
 static int set_loginuid(pam_handle_t *pamh, uid_t uid)
 {
-	int fd, count, rc = 0;
-	char loginuid[24];
+	int fd, count, rc = PAM_SESSION_ERR;
+	char loginuid[24], buf[24];
+	static const char host_uid_map[] = "         0          0 4294967295\n";
+	char uid_map[sizeof(host_uid_map)];
+
+	/* loginuid in user namespaces currently isn't writable and in some
+	   case, not even readable, so consider any failure as ignorable (but try
+	   anyway, in case we hit a kernel which supports it). */
+	fd = open("/proc/self/uid_map", O_RDONLY);
+	if (fd >= 0) {
+		count = pam_modutil_read(fd, uid_map, sizeof(uid_map));
+		if (strncmp(uid_map, host_uid_map, count) != 0)
+			rc = PAM_IGNORE;
+		close(fd);
+	}
 
-	count = snprintf(loginuid, sizeof(loginuid), "%lu", (unsigned long)uid);
-	fd = open("/proc/self/loginuid", O_NOFOLLOW|O_WRONLY|O_TRUNC);
+	fd = open("/proc/self/loginuid", O_NOFOLLOW|O_RDWR);
 	if (fd < 0) {
-		if (errno != ENOENT) {
-			rc = 1;
-			pam_syslog(pamh, LOG_ERR,
-				   "Cannot open /proc/self/loginuid: %m");
+		if (errno == ENOENT) {
+			rc = PAM_IGNORE;
+		}
+		if (rc != PAM_IGNORE) {
+			pam_syslog(pamh, LOG_ERR, "Cannot open %s: %m",
+				   "/proc/self/loginuid");
 		}
 		return rc;
 	}
-	if (pam_modutil_write(fd, loginuid, count) != count)
-		rc = 1;
+
+	count = snprintf(loginuid, sizeof(loginuid), "%lu", (unsigned long)uid);
+	if (pam_modutil_read(fd, buf, sizeof(buf)) == count &&
+	    memcmp(buf, loginuid, count) == 0) {
+		rc = PAM_SUCCESS;
+		goto done;	/* already correct */
+	}
+	if (lseek(fd, 0, SEEK_SET) == 0 && ftruncate(fd, 0) == 0 &&
+	    pam_modutil_write(fd, loginuid, count) == count) {
+		rc = PAM_SUCCESS;
+	} else {
+		if (rc != PAM_IGNORE) {
+			pam_syslog(pamh, LOG_ERR, "Error writing %s: %m",
+				   "/proc/self/loginuid");
+		}
+	}
+ done:
 	close(fd);
 	return rc;
 }
@@ -165,6 +196,7 @@
 {
         const char *user = NULL;
 	struct passwd *pwd;
+	int ret;
 #ifdef HAVE_LIBAUDIT
 	int require_auditd = 0;
 #endif
@@ -183,9 +215,14 @@
 		return PAM_SESSION_ERR;
 	}
 
-	if (set_loginuid(pamh, pwd->pw_uid)) {
-		pam_syslog(pamh, LOG_ERR, "set_loginuid failed\n");
-		return PAM_SESSION_ERR;
+	ret = set_loginuid(pamh, pwd->pw_uid);
+	switch (ret) {
+		case PAM_SUCCESS:
+		case PAM_IGNORE:
+			break;
+		default:
+			pam_syslog(pamh, LOG_ERR, "set_loginuid failed");
+			return ret;
 	}
 
 #ifdef HAVE_LIBAUDIT
@@ -195,11 +232,12 @@
 		argv++;
 	}
 
-	if (require_auditd)
-		return check_auditd();
-	else
+	if (require_auditd) {
+		int rc = check_auditd();
+		return rc != PAM_SUCCESS ? rc : ret;
+	} else
 #endif
-		return PAM_SUCCESS;
+		return ret;
 }
 
 /*
