[PATCH] mount.ecryptfs_private.c: add per-user locking

I have switched my email address to @canonical.com in the header.

This patch adds a simple locking mechanism, that allows a user to only
run one of mount.ecryptfs_private or umount.ecryptfs_private at a time.

This lock is intended to avoid race conditions where a directory would
be mounted/unmounted simultaneously during the course of the running
program.  Note that the current locking mechanism only protects a user
from multiple instances of himself; root could still produce a race by
perhaps mounting/unmounting the ecryptfs_private directory while the
user is running this script.  Also it does not solve the race where
device/mount permissions might change during the course of the run.
That check (check_ownerships()) is merely provided as a convenience to
the user--there are no privilege escalation implications of that race.

This patch creates two functions, acquire_lock() and release_lock(),
which will create/delete a lock file:
* /var/lock/ecryptfs_private.$USERNAME

Should lock acquisition fail, the program will return 1 rather than
sleeping and retrying.  Where such behavior is desired, that should be
handled by a wrapper program or calling script.  The setuid-nature of
this program demands that it run-to-completion.

This patch re-factored the main() function such that main() obtains and
releases the lock, calling locked_main() in the process.  The
overwhelming majority of the functionality that used to be in main() is
now in locked_main().

A fork()/wait() is now required surrounding the execl(unmount) call in
order to release the lock after that exec completes.

The patch also re-factors the code in check_ownerships() such that it
can be called repeatedly on different directory paths.

Inline documentation was updated accordingly.

Signed-off-by: Dustin Kirkland <[EMAIL PROTECTED]>



-- 
:-Dustin

Dustin Kirkland
Ubuntu Server Developer
Canonical, LTD
[EMAIL PROTECTED]
GPG: 1024D/83A61194
diff --git a/src/utils/mount.ecryptfs_private.c b/src/utils/mount.ecryptfs_private.c
index 108fd3f..91b00a3 100644
--- a/src/utils/mount.ecryptfs_private.c
+++ b/src/utils/mount.ecryptfs_private.c
@@ -4,7 +4,7 @@
  *
  * Copyright (C) 2008 Canonical Ltd.
  *
- * This code was originally written by Dustin Kirkland <[EMAIL PROTECTED]>.
+ * This code was originally written by Dustin Kirkland <[EMAIL PROTECTED]>.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -28,6 +28,7 @@
 #include <sys/mount.h>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <fcntl.h>
 #include <keyutils.h>
 #include <mntent.h>
 #include <pwd.h>
@@ -45,6 +46,7 @@
 #define KEY_CIPHER "aes"
 #define PRIVATE_DIR "Private"
 #define FSTYPE "ecryptfs"
+#define LOCK_FILE "/var/lock/ecryptfs_private"
 
 
 int check_username(char *u) {
@@ -133,13 +135,13 @@ char *fetch_sig(char *pw_dir) {
 }
 
 
-int check_ownerships(int uid, char *dev, char *mnt) {
-/* Check ownership of device and mount point.
+int check_ownerships(int uid, char *dir) {
+/* Check ownership of device or mount point.
  * Return 0 if everything is in order, 1 on error.
  */
 	struct stat s;
-	if (stat(dev, &s) != 0) {
-		fputs("Cannot examine encrypted directory\n", stderr);
+	if (stat(dir, &s) != 0) {
+		fputs("Cannot examine directory\n", stderr);
 		return 1;
 	}
 	if (!S_ISDIR(s.st_mode)) {
@@ -147,15 +149,7 @@ int check_ownerships(int uid, char *dev, char *mnt) {
 		return 1;
 	}
 	if (s.st_uid != uid) {
-		fputs("You do not own that encrypted directory\n", stderr);
-		return 1;
-	}
-	if (stat(mnt, &s) != 0) {
-		fputs("Cannot examine mount directory\n", stderr);
-		return 1;
-	}
-	if (s.st_uid != uid) {
-		fputs("You do not own that mount directory\n", stderr);
+		fputs("You do not own that directory\n", stderr);
 		return 1;
 	}
 	return 0;
@@ -253,20 +247,39 @@ int update_mtab(char *dev, char *mnt, char *opt) {
 	return 0;
 }
 
+int acquire_lock(char *lockfile) {
+/* Simple lock acquisition function.
+ * Return 1 if the lock cannot be acquire.  Note that we do not sleep or
+ * wait to attain the lock.  Due to the setuid-nature of this program,
+ * we want to run-to-completion.  Wrappers or callers of this program should
+ * handle any necessary retries.
+ * Return 0 on success.
+ */
+	int fh;
+	fh = open(lockfile, O_CREAT|O_EXCL, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
+	if (fh < 0) {
+		perror(lockfile);
+		return 1;
+	}
+	close(fh);
+	return 0;
+}
 
-/* This program is a setuid-executable allowing a non-privileged user to mount
- * and unmount an ecryptfs private directory.  This program is necessary to
- * keep from adding such entries to /etc/fstab.
- *
- * A single executable is created and hardlinked to two different names.
- * The mode of operation (mounting|unmounting) is determined by examining 
- * the name of the executable.  "Mounting" mode is assumed, unless the
- * executable contains the string "umount".
- * Example:
- *   /sbin/mount.ecryptfs_private
- *   /sbin/umount.ecryptfs_private
+void release_lock(char *lockfile) {
+/* Simple lock release function.
+ * If we cannot delete the lock, print an error message.  However, there's
+ * not much more we can do, as we should be euid=0 here.
+ */
+	if (unlink(lockfile) != 0) {
+		perror("unlink");
+	}
+}
+
+
+/* This function performs the bulk of the work in this program, and only runs
+ * if an appropriate lock is established.
  *
- * At the moment, this program:
+ * At the moment, this function:
  *  - mounts ~/.Private onto ~/Private
  *    - as an ecryptfs filesystem
  *    - using the AES cipher
@@ -282,53 +295,29 @@ int update_mtab(char *dev, char *mnt, char *opt) {
  *      - has the signature's key in his keyring
  *      - owns both ~/.Private and ~/Private
  *      - is currently mounted
- *
- * The only setuid operations in this program are:
- *  a) mounting
- *  b) unmounting
- *  c) updating /etc/mtab
+ *  - returns 0 on success
+ *  - returns 1 on any failure
  */
-int main(int argc, char *argv[]) {
-	int uid, rc, mounting;
-	struct passwd *pwd;
+int locked_main(int mounting, struct passwd *pwd) {
 	char *dev, *mnt, *opt;
 	char *sig;
-
-	uid = getuid();
-	if ((pwd = getpwuid(uid)) == NULL) {
-		perror("getpwuid");
-		return 1;
-	}
+	int pid, rc;
 
 	/* Non-privileged effective uid is sufficient for all but the code
  	 * that mounts, unmounts, and updates /etc/mtab.
 	 * Run at a lower privilege until we need it.
 	 */
-	if (seteuid(uid)<0 || geteuid()!=uid) {
-		perror("setuid");
+	if (seteuid(pwd->pw_uid)<0 || geteuid()!=pwd->pw_uid) {
+		perror("seteuid");
 		return 1;
 	}
 
-	if (check_username(pwd->pw_name) != 0) {
-		/* Must protect against a crafted user=john,suid from entering
-		 * filesystem options
-		 */
-		return 1;
-	}
-
-	/* Determine if mounting or unmounting by looking at the invocation */
-	if (strstr(argv[0], "umount") == NULL) {
-		mounting = 1;
-	} else {
-		mounting = 0;
-	}
-
 	/* Fetch signature from file */
 	if ((sig = fetch_sig(pwd->pw_dir)) == NULL) {
 		return 1;
 	}
 
-	/* Construct device, mount point, and mount options */
+	/* Construct device, mount point, mount options */
 	if (
 	    (asprintf(&dev, "%s/.%s", pwd->pw_dir, PRIVATE_DIR) < 0) || 
 	    dev == NULL) {
@@ -350,7 +339,8 @@ int main(int argc, char *argv[]) {
 	}
 
 	/* Check ownership of dev and mnt */
-	if (check_ownerships(uid, dev, mnt) != 0) {
+	if (check_ownerships(pwd->pw_uid, dev) != 0 ||
+	    check_ownerships(pwd->pw_uid, mnt) != 0 ) {
 		return 1;
 	}
 
@@ -375,6 +365,8 @@ int main(int argc, char *argv[]) {
 			if (update_mtab(dev, mnt, opt) != 0) {
 				return 1;
 			}
+			/* Successful mount */
+			return 0;
 		} else {
 			perror("mount");
 			return 1;
@@ -391,9 +383,94 @@ int main(int argc, char *argv[]) {
 		 * Do not use the umount.ecryptfs helper (-i).
  		 */
 		setresuid(0,0,0);
-		execl("/bin/umount", "umount", "-i", mnt, NULL);
-		perror("execl unmount failed");
+		if ((pid = fork()) < 0) {
+			perror("fork");
+			return 1;
+		}
+		if (pid == 0) {
+			execl("/bin/umount", "umount", "-i", mnt, NULL);
+			perror("execl unmount failed");
+			return 1;
+		} else {
+			wait(&rc);
+			return rc;
+		}
+	}
+	/* It should be impossible to reach here */
+	return 1;
+}
+
+
+/* This program is a setuid-executable allowing a non-privileged user to mount
+ * and unmount an ecryptfs private directory.  This program is necessary to
+ * keep from adding such entries to /etc/fstab.
+ *
+ * A single executable is created and hardlinked to two different names.
+ * The mode of operation (mounting|unmounting) is determined by examining 
+ * the name of the executable.  "Mounting" mode is assumed, unless the
+ * executable contains the string "umount".
+ * Example:
+ *   /sbin/mount.ecryptfs_private
+ *   /sbin/umount.ecryptfs_private
+ *
+ * A lock in /var/lock/ecryptfs_private.USERNAME is used to ensure that a given
+ * user does not run this program more than once at a time, due to some race
+ * that can arise in such situations.
+ *
+ * The only setuid operations in this program are:
+ *  a) acquiring the lock
+ *  b) mounting
+ *  c) unmounting
+ *  d) updating /etc/mtab
+ *  e) releasing the lock
+ *
+ *  The sanity checking of the environment, mounting, and umounting is
+ *  performed by the locked_main() function, once the lock is established.
+ *  This functional separation helps ensure that the lock is cleaned up in
+ *  ALL failure situations by handling it in a single place in the main()
+ *  function.
+ */
+int main(int argc, char *argv[]) {
+	int mounting, uid, rc;
+	struct passwd *pwd;
+	char *lockfile;
+
+	/* Determine if mounting or unmounting by looking at the invocation */
+	if (strstr(argv[0], "umount") == NULL) {
+		mounting = 1;
+	} else {
+		mounting = 0;
+	}
+	uid = getuid();
+	if ((pwd = getpwuid(uid)) == NULL) {
+		perror("getpwuid");
 		return 1;
 	}
-	return 0;
+	if (check_username(pwd->pw_name) != 0) {
+		/* Must protect against a crafted user=john,suid from entering
+		 * filesystem options
+		 */
+		return 1;
+	}
+
+	/* Acquire lock file, assuring that only one instance of this program
+	 * per user runs at once.
+	 */
+	if (
+	    (asprintf(&lockfile, "%s.%s", LOCK_FILE, pwd->pw_name) < 0) ||
+	    lockfile == NULL) {
+		perror("asprintf (lockfile)");
+		return 1;
+	}
+	if (acquire_lock(lockfile) != 0) {
+		return 1;
+	}
+	/*******************************************************************/
+	rc = locked_main(mounting, pwd);
+	/*******************************************************************/
+	if (seteuid(0)<0 || geteuid()!=0) {
+		perror("seteuid");
+	}
+	release_lock(lockfile);
+	return rc;
 }

Attachment: signature.asc
Description: This is a digitally signed message part

-------------------------------------------------------------------------
Check out the new SourceForge.net Marketplace.
It's the best place to buy or sell services for
just about anything Open Source.
http://sourceforge.net/services/buy/index.php
_______________________________________________
eCryptfs-devel mailing list
eCryptfs-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/ecryptfs-devel

Reply via email to