The following patch tightens the pledges for ftp(1). This provides some additional guarantees, including that ftp(1) cannot spawn child processes. This is a significant security win for sysupgrade(8).
I hope I did not mess up the diff - this is my first time submitting one. Index: usr.bin/ftp/main.c =================================================================== RCS file: /cvs/src/usr.bin/ftp/main.c,v retrieving revision 1.131 diff -u -p -u -r1.131 main.c --- usr.bin/ftp/main.c 11 Feb 2020 18:41:39 -0000 1.131 +++ usr.bin/ftp/main.c 2 May 2020 19:31:40 -0000 @@ -483,27 +483,39 @@ main(volatile int argc, char *argv[]) (void)signal(SIGWINCH, setttywidth); if (argc > 0) { + int i, needs_exec = 0; if (isurl(argv[0])) { - if (pipeout) { #ifndef SMALL - if (pledge("stdio rpath dns tty inet proc exec fattr", - NULL) == -1) - err(1, "pledge"); -#else - if (pledge("stdio rpath dns tty inet fattr", - NULL) == -1) - err(1, "pledge"); + for (i = 0; i < argc; ++i) { + if (*(argv[i]) == '\0') + errx(1, "empty URL not allowed"); + else if (strncasecmp("http:/", argv[i], 6) && + strncasecmp("https:/", argv[i], 7) && + strncasecmp("file:", argv[i], 5) && + argv[i][strlen(argv[i])] == '/') + needs_exec = 1; + } #endif + if (pipeout) { + if (needs_exec) { + if (pledge("stdio rpath dns tty inet proc exec fattr", + NULL) == -1) + err(1, "pledge"); + } else { + if (pledge("stdio rpath dns tty inet fattr", + NULL) == -1) + err(1, "pledge"); + } } else { -#ifndef SMALL - if (pledge("stdio rpath wpath cpath dns tty inet proc exec fattr", - NULL) == -1) - err(1, "pledge"); -#else - if (pledge("stdio rpath wpath cpath dns tty inet fattr", - NULL) == -1) - err(1, "pledge"); -#endif + if (needs_exec) { + if (pledge("stdio rpath wpath cpath dns tty inet proc exec fattr", + NULL) == -1) + err(1, "pledge"); + } else { + if (pledge("stdio rpath wpath cpath dns tty inet fattr", + NULL) == -1) + err(1, "pledge"); + } } rval = auto_fetch(argc, argv, outfile);