Treat an ID of -1 as invalid since that means "no change".
Fixes CVE-2019-14287.
Found by Joe Vennix from Apple Information Security.

Index: sudo-1.7.6p2/Makefile.in
===================================================================
--- sudo-1.7.6p2.orig/Makefile.in	2019-10-17 16:25:10.288652410 +0200
+++ sudo-1.7.6p2/Makefile.in	2019-10-17 16:25:39.144810640 +0200
@@ -116,7 +116,7 @@ SRCS = aix.c alias.c alloc.c audit.c boo
        sudo_edit.c sudo_nss.c term.c testsudoers.c tgetpass.c toke.c toke.l \
        toke_util.c tsgetgrpw.c utimes.c vasgroups.c visudo.c zero_bytes.c \
        redblack.c selinux.c sesh.c sudoreplay.c getdate.c getdate.y getline.c \
-       timestr.c ttyname.c $(AUTH_SRCS)
+       timestr.c ttyname.c atoid.c $(AUTH_SRCS)
 
 AUTH_SRCS = auth/afs.c auth/aix_auth.c auth/bsdauth.c auth/dce.c auth/fwtk.c \
 	    auth/kerb4.c auth/kerb5.c auth/pam.c auth/passwd.c auth/rfc1938.c \
@@ -139,7 +139,7 @@ COMMON_OBJS = alias.o alloc.o defaults.o
 SUDO_OBJS = $(AUTH_OBJS) @SUDO_OBJS@ audit.o boottime.o check.o env.o \
 	    exec.o gettime.o goodpath.o fileops.o find_path.o \
 	    interfaces.o lbuf.o logging.o parse.o parse_args.o set_perms.o \
-	    sudo.o sudo_edit.o sudo_nss.o tgetpass.o ttyname.o
+	    sudo.o sudo_edit.o sudo_nss.o tgetpass.o ttyname.o atoid.o
 
 VISUDO_OBJS = visudo.o fileops.o gettime.o goodpath.o find_path.o
 
@@ -435,6 +435,8 @@ securid5.o: $(authdir)/securid5.c $(AUTH
 	$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(authdir)/securid5.c
 sia.o: $(authdir)/sia.c $(AUTHDEP)
 	$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(authdir)/sia.c
+atoid.o: $(srcdir)/atoid.c $(SUDODEP)
+	$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(srcdir)/atoid.c
 
 # Zlib dependencies
 adler32.o: $(srcdir)/zlib/zutil.h $(srcdir)/zlib/zlib.h zlib/zconf.h
Index: sudo-1.7.6p2/sudo.c
===================================================================
--- sudo-1.7.6p2.orig/sudo.c	2019-10-17 16:25:10.288652410 +0200
+++ sudo-1.7.6p2/sudo.c	2019-10-17 16:25:13.708671095 +0200
@@ -120,6 +120,8 @@ static void show_version		__P((void));
 static void create_admin_success_flag	__P((void));
 extern int sudo_edit			__P((int, char **, char **));
 int run_command __P((const char *path, char *argv[], char *envp[], uid_t uid, int dowait)); /* XXX should be in sudo.h */
+extern id_t atoid(const char *str, const char *sep, char **endp, const char **errstr);
+
 
 /*
  * Globals
@@ -1210,17 +1212,25 @@ static void
 set_runaspw(user)
     char *user;
 {
-    if (runas_pw != NULL)
-	pw_delref(runas_pw);
+    struct passwd *pw = NULL;
+
     if (*user == '#') {
-	if ((runas_pw = sudo_getpwuid(atoi(user + 1))) == NULL)
-	    runas_pw = sudo_fakepwnam(user, runas_gr ? runas_gr->gr_gid : 0);
-    } else {
-	if ((runas_pw = sudo_getpwnam(user)) == NULL) {
+	const char *errstr;
+	uid_t uid = atoid(user + 1, NULL, NULL, &errstr);
+	if (errstr == NULL) {
+	    if ((pw = sudo_getpwuid(uid)) == NULL)
+		pw = sudo_fakepwnam(user, runas_gr ? runas_gr->gr_gid : 0);
+	}
+    }
+    if (pw == NULL) {
+	if ((pw = sudo_getpwnam(user)) == NULL) {
 	    audit_failure(NewArgv, "unknown user: %s", user);
 	    log_error(NO_MAIL|MSG_ONLY, "unknown user: %s", user);
-	}
+        }
     }
+    if (runas_pw != NULL)
+	pw_delref(runas_pw);
+    runas_pw = pw;
 }
 
 /*
Index: sudo-1.7.6p2/atoid.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ sudo-1.7.6p2/atoid.c	2019-10-17 16:25:13.708671095 +0200
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2013 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+
+#include <stdio.h>
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# ifdef HAVE_STDLIB_H
+#  include <stdlib.h>
+# endif
+#endif /* STDC_HEADERS */
+#if defined(HAVE_STDINT_H)
+# include <stdint.h>
+#elif defined(HAVE_INTTYPES_H)
+# include <inttypes.h>
+#endif
+#include <stdbool.h>
+#include <errno.h>
+#include <limits.h>
+
+#include "missing.h"
+
+/*
+ * Make sure that the ID ends with a valid separator char.
+ */
+static bool
+valid_separator(const char *p, const char *ep, const char *sep)
+{
+    bool valid = false;
+
+    if (ep != p) {
+	/* check for valid separator (including '\0') */
+	if (sep == NULL)
+	    sep = "";
+	do {
+	    if (*ep == *sep)
+		valid = true;
+	} while (*sep++ != '\0');
+    }
+    return valid;
+}
+
+/*
+ * Parse a uid/gid in string form.
+ * If sep is non-NULL, it contains valid separator characters (e.g. comma, space)
+ * If endp is non-NULL it is set to the next char after the ID.
+ * On success, returns the parsed ID and clears errstr.
+ * On error, returns 0 and sets errstr.
+ */
+id_t
+atoid(const char *p, const char *sep, char **endp, const char **errstr)
+{
+    char *ep;
+    id_t rval = 0;
+
+    errno = 0;
+    if (*p == '-') {
+	long lval = strtol(p, &ep, 10);
+	if ((errno == ERANGE && lval == LONG_MAX) || lval > INT_MAX) {
+	    errno = ERANGE;
+	    if (errstr != NULL)
+		*errstr = "value too large";
+	    goto done;
+	}
+	if ((errno == ERANGE && lval == LONG_MIN) || lval < INT_MIN) {
+	    errno = ERANGE;
+	    if (errstr != NULL)
+		*errstr = "value too small";
+	    goto done;
+	}
+
+	/* Disallow id -1, which means "no change". */
+	if (!valid_separator(p, ep, sep) || lval == -1) {
+	    if (errstr != NULL)
+		*errstr = "invalid value";
+	    errno = EINVAL;
+	    goto done;
+	}
+	rval = (id_t)lval;
+    } else {
+	unsigned long ulval = strtoul(p, &ep, 10);
+	if ((errno == ERANGE && ulval == ULONG_MAX) || ulval > UINT_MAX) {
+	    errno = ERANGE;
+	    if (errstr != NULL)
+		*errstr = "value too large";
+	    goto done;
+	}
+
+	/* Disallow id -1, which means "no change". */
+	if (!valid_separator(p, ep, sep) || ulval == UINT_MAX) {
+	    if (errstr != NULL)
+		*errstr = "invalid value";
+	    errno = EINVAL;
+	    goto done;
+	}
+	rval = (id_t)ulval;
+    }
+    if (errstr != NULL)
+	*errstr = NULL;
+    if (endp != NULL)
+	*endp = ep;
+done:
+    return rval;
+}
