Commit message: CVE-2015-1331: lxclock: use /run/lxc/lock rather than /run/lock/lxc

This prevents an unprivileged user to use LXC to create arbitrary file
on the filesystem.

Signed-off-by: Serge Hallyn <serge.hallyn@ubuntu.com>
Signed-off-by: Tyler Hicks <tyhicks@canonical.com>
Acked-by: Stéphane Graber <stgraber@ubuntu.com>
CVE: CVE-2015-1331
CWE: CWE-59
Vulnerability description: lxclock.c in LXC 1.1.2 and earlier allows local users to create arbitrary files via a symlink attack on /run/lock/lxc/*.

Project: lxc
File: lxclock.c

int container_disk_lock(struct lxc_container *c)
{
	int ret;

	if ((ret = lxclock(c->privlock, 0)))
		return ret;
	if ((ret = lxclock(c->slock, 0))) {
		lxcunlock(c->privlock);
		return ret;
	}
	return 0;
}

Project: lxc
File: lxclock.c

void container_disk_unlock(struct lxc_container *c)
{
	lxcunlock(c->slock);
	lxcunlock(c->privlock);
}

Project: lxc
File: lxclock.c

int container_mem_lock(struct lxc_container *c)
{
	return lxclock(c->privlock, 0);
}

Project: lxc
File: lxclock.c

void container_mem_unlock(struct lxc_container *c)
{
	lxcunlock(c->privlock);
}

Project: lxc
File: lxclock.c

static inline void dump_stacktrace(void) {;}

Project: lxc
File: lxclock.c

static void lock_mutex(pthread_mutex_t *l)
{
	int ret;

	if ((ret = pthread_mutex_lock(l)) != 0) {
		fprintf(stderr, "pthread_mutex_lock returned:%d %s\n", ret, strerror(ret));
		dump_stacktrace();
		exit(1);
	}
}

Project: lxc
File: lxclock.c

static sem_t *lxc_new_unnamed_sem(void)
{
	sem_t *s;
	int ret;

	s = malloc(sizeof(*s));
	if (!s)
		return NULL;
	ret = sem_init(s, 0, 1);
	if (ret) {
		free(s);
		return NULL;
	}
	return s;
}

Project: lxc
File: lxclock.c

struct lxc_lock *lxc_newlock(const char *lxcpath, const char *name)
{
	struct lxc_lock *l;

	l = malloc(sizeof(*l));
	if (!l)
		goto out;

	if (!name) {
		l->type = LXC_LOCK_ANON_SEM;
		l->u.sem = lxc_new_unnamed_sem();
		if (!l->u.sem) {
			free(l);
			l = NULL;
		}
		goto out;
	}

	l->type = LXC_LOCK_FLOCK;
	l->u.f.fname = lxclock_name(lxcpath, name);
	if (!l->u.f.fname) {
		free(l);
		l = NULL;
		goto out;
	}
	l->u.f.fd = -1;

out:
	return l;
}

Project: lxc
File: lxclock.c

void lxc_putlock(struct lxc_lock *l)
{
	if (!l)
		return;
	switch(l->type) {
	case LXC_LOCK_ANON_SEM:
		if (l->u.sem) {
			sem_destroy(l->u.sem);
			free(l->u.sem);
			l->u.sem = NULL;
		}
		break;
	case LXC_LOCK_FLOCK:
		if (l->u.f.fd != -1) {
			close(l->u.f.fd);
			l->u.f.fd = -1;
		}
		free(l->u.f.fname);
		l->u.f.fname = NULL;
		break;
	}
	free(l);
}

Project: lxc
File: lxclock.c

int lxclock(struct lxc_lock *l, int timeout)
{
	int ret = -1, saved_errno = errno;
	struct flock lk;

	switch(l->type) {
	case LXC_LOCK_ANON_SEM:
		if (!timeout) {
			ret = sem_wait(l->u.sem);
			if (ret == -1)
				saved_errno = errno;
		} else {
			struct timespec ts;
			if (clock_gettime(CLOCK_REALTIME, &ts) == -1) {
				ret = -2;
				goto out;
			}
			ts.tv_sec += timeout;
			ret = sem_timedwait(l->u.sem, &ts);
			if (ret == -1)
				saved_errno = errno;
		}
		break;
	case LXC_LOCK_FLOCK:
		ret = -2;
		if (timeout) {
			ERROR("Error: timeout not supported with flock");
			ret = -2;
			goto out;
		}
		if (!l->u.f.fname) {
			ERROR("Error: filename not set for flock");
			ret = -2;
			goto out;
		}
		if (l->u.f.fd == -1) {
			l->u.f.fd = open(l->u.f.fname, O_RDWR|O_CREAT,
					S_IWUSR | S_IRUSR);
			if (l->u.f.fd == -1) {
				ERROR("Error opening %s", l->u.f.fname);
				goto out;
			}
		}
		lk.l_type = F_WRLCK;
		lk.l_whence = SEEK_SET;
		lk.l_start = 0;
		lk.l_len = 0;
		ret = fcntl(l->u.f.fd, F_SETLKW, &lk);
		if (ret == -1)
			saved_errno = errno;
		break;
	}

out:
	errno = saved_errno;
	return ret;
}

Project: lxc
File: lxclock.c

int lxcunlock(struct lxc_lock *l)
{
	int ret = 0, saved_errno = errno;
	struct flock lk;

	switch(l->type) {
	case LXC_LOCK_ANON_SEM:
		if (!l->u.sem)
			ret = -2;
		else {
			ret = sem_post(l->u.sem);
			saved_errno = errno;
		}
		break;
	case LXC_LOCK_FLOCK:
		if (l->u.f.fd != -1) {
			lk.l_type = F_UNLCK;
			lk.l_whence = SEEK_SET;
			lk.l_start = 0;
			lk.l_len = 0;
			ret = fcntl(l->u.f.fd, F_SETLK, &lk);
			if (ret < 0)
				saved_errno = errno;
			close(l->u.f.fd);
			l->u.f.fd = -1;
		} else
			ret = -2;
		break;
	}

	errno = saved_errno;
	return ret;
}

Project: lxc
File: lxclock.c

static void process_lock_setup_atfork(void)
{
	pthread_atfork(process_lock, process_unlock, process_unlock);
}

Project: lxc
File: lxclock.c

void process_unlock(void)
{
	unlock_mutex(&thread_mutex);
}

Project: lxc
File: lxclock.c

static void unlock_mutex(pthread_mutex_t *l)
{
	int ret;

	if ((ret = pthread_mutex_unlock(l)) != 0) {
		fprintf(stderr, "pthread_mutex_unlock returned:%d %s\n", ret, strerror(ret));
		dump_stacktrace();
		exit(1);
	}
}

Project: lxc
File: locktests.c

static void test_two_locks(void)
{
	struct lxc_lock *l;
	pid_t pid;
	int ret, status;
	int p[2];
	char c;

	if (pipe(p) < 0)
		exit(1);
	if ((pid = fork()) < 0)
		exit(1);
	if (pid == 0) {
		if (read(p[0], &c, 1) < 0) {
			perror("read");
			exit(1);
		}
		l = lxc_newlock("/tmp", "lxctest-sem");
		if (!l) {
			fprintf(stderr, "%d: child: failed to create lock\n", __LINE__);
			exit(1);
		}
		if (lxclock(l, 0) < 0) {
			fprintf(stderr, "%d: child: failed to grab lock\n", __LINE__);
			exit(1);
		}
		fprintf(stderr, "%d: child: grabbed lock\n", __LINE__);
		exit(0);
	}
	l = lxc_newlock("/tmp", "lxctest-sem");
	if (!l) {
		fprintf(stderr, "%d: failed to create lock\n", __LINE__);
		exit(1);
	}
	if (lxclock(l, 0) < 0) {
		fprintf(stderr, "%d; failed to get lock\n", __LINE__);
		exit(1);
	}
	if (write(p[1], "a", 1) < 0) {
		perror("write");
		exit(1);
	}
	sleep(3);
	ret = waitpid(pid, &status, WNOHANG);
	if (ret == pid) { // task exited
		if (WIFEXITED(status)) {
			printf("%d exited normally with exit code %d\n", pid,
				WEXITSTATUS(status));
			if (WEXITSTATUS(status) == 0)
				exit(1);
		} else
			printf("%d did not exit normally\n", pid);
		return;
	} else if (ret < 0) {
		perror("waitpid");
		exit(1);
	}
	kill(pid, SIGKILL);
	wait(&status);
	close(p[1]);
	close(p[0]);
	lxcunlock(l);
	lxc_putlock(l);
}