CVE-2015-1340 (Go, published 2019-04-22T16:29Z)
Description: value: LXD before version 0.19-0ubuntu5 doUidshiftIntoContainer() has an unsafe Chmod() call that races against the stat in the Filepath.Walk() function. A symbolic link created in that window could cause any file on the system to have any mode of the attacker's choice.
Severity: MEDIUM (CVSS v3 base score: 8.1)
Weakness: CWE-362 \u2014 Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition')
The product contains a code sequence that can run concurrently with other code, and the code sequence requires temporary, exclusive access to a shared resource, but a timing window exists in which the shared resource can be modified by another code sequence that is operating concurrently.
Repository: https://github.com/lxc/lxd
Files changed:
shared/idmapset_linux.go
shared/util_linux.go
Commit message: CVE-2015-1340: Fix race condition between fchown and chmod in idmapset

Shifting a container filesystem in an environment where a user can
modify the container's filesystem as it's being shifted (for example if
the LXD server's / was shared over the network) would allow them to make
use of a race (TOCTOU) between the chown and chmod operations, allowing
for an arbitrary path to be chmod-ed rather than the planned container
path.

The fix is to use a file descriptor to the entry being processed,
validate that the entry itself is sane and then interact with the fd.

This is CVE-2015-1340

Reported-by: Seth Arnold
Signed-off-by: Stéphane Graber <stgraber@ubuntu.com>


Vulnerable code:
			err = os.Lchown(path, int(newuid), int(newgid))
			if err == nil {
				m := fi.Mode()
				if m&os.ModeSymlink == 0 {
					err = os.Chmod(path, m)
					if err != nil {
						fmt.Printf("Error resetting mode on %q, continuing\n", path)
					}
				}
#include <stdio.h>

Fixed code:
	dir = strings.TrimRight(dir, "/")

			err = ShiftOwner(dir, path, int(newuid), int(newgid))
			if err != nil {
				return err
#include <sys/stat.h>
#include <limits.h>

#ifndef AT_SYMLINK_FOLLOW
#define AT_SYMLINK_FOLLOW    0x400
#endif

#ifndef AT_EMPTY_PATH
#define AT_EMPTY_PATH       0x1000
#endif

int shiftowner(char *basepath, char *path, int uid, int gid) {
	struct stat sb;
	int fd, r;
	char fdpath[PATH_MAX];
	char realpath[PATH_MAX];

	fd = open(path, O_PATH|O_NOFOLLOW);
	if (fd < 0 ) {
		perror("Failed open");
		return 1;
	}

	r = sprintf(fdpath, "/proc/self/fd/%d", fd);
	if (r < 0) {
		perror("Failed sprintf");
		close(fd);
		return 1;
	}

	r = readlink(fdpath, realpath, PATH_MAX);
	if (r < 0) {
		perror("Failed readlink");
		close(fd);
		return 1;
	}

	if (strlen(realpath) < strlen(basepath)) {
		printf("Invalid path, source is outside of basepath.\n");
		close(fd);
		return 1;
	}

	if (strncmp(realpath, basepath, strlen(basepath))) {
		printf("Invalid path, source is outside of basepath.\n");
		close(fd);
		return 1;
	}

	r = fstat(fd, &sb);
	if (r < 0) {
		perror("Failed fstat");
		close(fd);
		return 1;
	}

	r = fchownat(fd, "", uid, gid, AT_EMPTY_PATH|AT_SYMLINK_NOFOLLOW);
	if (r < 0) {
		perror("Failed chown");
		close(fd);
		return 1;
	}

	if (!S_ISLNK(sb.st_mode)) {
		r = chmod(fdpath, sb.st_mode);
		if (r < 0) {
			perror("Failed chmod");
			close(fd);
			return 1;
		}
	}

	close(fd);
	return 0;
}
func ShiftOwner(basepath string, path string, uid int, gid int) error {
	r := C.shiftowner(C.CString(basepath), C.CString(path), C.int(uid), C.int(gid))
	if r != 0 {
		return fmt.Errorf("Failed to change ownership of: %s", path)
	}
	return nil
}