CVE-2020-12062 (C, published 2020-06-01T16:15Z)
Description: value: The scp client in OpenSSH 8.2 incorrectly sends duplicate responses to the server upon a utimes system call failure, which allows a malicious unprivileged user on the remote server to overwrite arbitrary files in the client's download directory by creating a crafted subdirectory anywhere on the remote server. The victim must use the command scp -rp to download a file hierarchy containing, anywhere inside, this crafted subdirectory. NOTE: the vendor points out that "this attack can achieve no more than a hostile peer is already able to achieve within the scp protocol" and "utimes does not fail under normal circumstances.
Severity: MEDIUM (CVSS v3 base score: 7.5)
Weakness: CWE-20 \u2014 Improper Input Validation
The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
Repository: https://github.com/openssh/openssh-portable
Files changed:
scp.c
Commit message: upstream: when receving a file in sink(), be careful to send at

most a single error response after the file has been opened. Otherwise the
source() and sink() can become desyncronised. Reported by Daniel Goujot,
Georges-Axel Jaloyan, Ryan Lahfa, and David Naccache.

ok deraadt@ markus@

OpenBSD-Commit-ID: 6c14d233c97349cb811a8f7921ded3ae7d9e0035


Vulnerable code:
/* $OpenBSD: scp.c,v 1.208 2020/04/30 17:07:10 markus Exp $ */
	enum {
		YES, NO, DISPLAYED
	} wrerr;
	int setimes, targisdir, wrerrno = 0;
		wrerr = NO;
				if (wrerr == NO) {
						wrerr = YES;
						wrerrno = errno;
		if (count != 0 && wrerr == NO &&
			wrerr = YES;
			wrerrno = errno;
		}
		if (wrerr == NO && (!exists || S_ISREG(stb.st_mode)) &&
		    ftruncate(ofd, size) != 0) {
			run_err("%s: truncate: %s", np, strerror(errno));
			wrerr = DISPLAYED;
					run_err("%s: set mode: %s",
					wrerr = DISPLAYED;
					run_err("%s: set mode: %s",
					wrerr = DISPLAYED;
		if (close(ofd) == -1) {
			wrerr = YES;
			wrerrno = errno;
		}
		if (setimes && wrerr == NO) {
				run_err("%s: set times: %s",
				wrerr = DISPLAYED;
		switch (wrerr) {
		case YES:
			run_err("%s: %s", np, strerror(wrerrno));
			break;
		case NO:
			break;
		case DISPLAYED:
			break;
		}

Fixed code:
/* $OpenBSD: scp.c,v 1.209 2020/05/01 06:31:42 djm Exp $ */
int note_err(const char *,...);
	int setimes, targisdir, wrerr;
		wrerr = 0;
		/*
		 * NB. do not use run_err() unless immediately followed by
		 * exit() below as it may send a spurious reply that might
		 * desyncronise us from the peer. Use note_err() instead.
		 */
				if (!wrerr) {
						note_err("%s: %s", np,
						    strerror(errno));
						wrerr = 1;
		if (count != 0 && !wrerr &&
			note_err("%s: %s", np, strerror(errno));
			wrerr = 1;
		if (!wrerr && (!exists || S_ISREG(stb.st_mode)) &&
		    ftruncate(ofd, size) != 0)
			note_err("%s: truncate: %s", np, strerror(errno));
					note_err("%s: set mode: %s",
					note_err("%s: set mode: %s",
		if (close(ofd) == -1)
			note_err(np, "%s: close: %s", np, strerror(errno));
		if (setimes && !wrerr) {
				note_err("%s: set times: %s",
		/* If no error was noted then signal success for this file */
		if (note_err(NULL) == 0)
/*
 * Notes a sink error for sending at the end of a file transfer. Returns 0 if
 * no error has been noted or -1 otherwise. Use note_err(NULL) to flush
 * any active error at the end of the transfer.
 */
int
note_err(const char *fmt, ...)
{
	static char *emsg;
	va_list ap;

	/* Replay any previously-noted error */
	if (fmt == NULL) {
		if (emsg == NULL)
			return 0;
		run_err("%s", emsg);
		free(emsg);
		emsg = NULL;
		return -1;
	}

	errs++;
	/* Prefer first-noted error */
	if (emsg != NULL)
		return -1;

	va_start(ap, fmt);
	vasnmprintf(&emsg, INT_MAX, NULL, fmt, ap);
	va_end(ap);
	return -1;
}