Hi, I started a review of ikeca.c about the use of system(3). But as there is a lot of others place of improvement in the code, I prefer to go step by step, and proposing patches only for one function at time.
The following patch is a review of ca_export() function. It adds: - xsnprintf() function: same as snprintf() but check the return, and call errx() on error. It corrects: - an invalid use of err() instead of errx() - an invalid mode in fcopy (644 instead of 0644) - check return for strlcpy (or add (void) casting on safe use), chmod(2), realpath(3), replace snprintf(3) by xsnprintf()... - convert system(3) to vfork(2)/execv(2) The reason of the use of vfork(2) instead of fork(2) is that I follow how system(3) is implemented (with vfork the parent is suspended until the child makes a call to execve(2) or an exit). Comments ? OK ? -- Sebastien Marie Index: ikeca.c =================================================================== RCS file: /cvs/src/usr.sbin/ikectl/ikeca.c,v retrieving revision 1.30 diff -u -p -r1.30 ikeca.c --- ikeca.c 16 Jan 2015 06:40:17 -0000 1.30 +++ ikeca.c 30 Jul 2015 09:39:44 -0000 @@ -18,6 +18,8 @@ #include <sys/types.h> #include <sys/stat.h> +#include <sys/wait.h> + #include <stdio.h> #include <unistd.h> #include <err.h> @@ -90,6 +92,20 @@ int fcopy(char *, char *, mode_t); int rm_dir(char *); int ca_hier(char *); +static void +xsnprintf(char *str, size_t size, const char *format, ...) +{ + va_list ap; + int ret; + + va_start(ap, format); + ret = vsnprintf(str, size, format, ap); + va_end(ap); + + if ((ret == -1) || ((size_t)ret >= size)) + errx(1, "vsnprintf: output truncated"); +} + int ca_delete(struct ca *ca) { @@ -550,20 +566,23 @@ ca_export(struct ca *ca, char *keyname, struct stat st; char *pass; char prev[_PASSWORD_LEN + 1]; - char cmd[PATH_MAX * 2]; char oname[PATH_MAX]; char src[PATH_MAX]; char dst[PATH_MAX]; char *p; char tpl[] = "/tmp/ikectl.XXXXXXXXXX"; - u_int i; int fd; + pid_t pid; + int status; + char *argv[18]; + int argc; + char *envp[2]; if (keyname != NULL) { if (strlcpy(oname, keyname, sizeof(oname)) >= sizeof(oname)) - err(1, "name too long"); + errx(1, "name too long"); } else { - strlcpy(oname, "ca", sizeof(oname)); + (void)strlcpy(oname, "ca", sizeof(oname)); } /* colons are not valid characters in windows filenames... */ @@ -577,99 +596,227 @@ ca_export(struct ca *ca, char *keyname, if (pass == NULL || *pass == '\0') err(1, "password not set"); - strlcpy(prev, pass, sizeof(prev)); + (void)strlcpy(prev, pass, sizeof(prev)); pass = getpass("Retype export passphrase:"); if (pass == NULL || strcmp(prev, pass) != 0) errx(1, "passphrase does not match!"); } if (keyname != NULL) { - snprintf(cmd, sizeof(cmd), "env EXPASS=%s %s pkcs12 -export" - " -name %s -CAfile %s/ca.crt -inkey %s/private/%s.key" - " -in %s/%s.crt -out %s/private/%s.pfx -passout env:EXPASS" - " -passin file:%s", pass, PATH_OPENSSL, keyname, - ca->sslpath, ca->sslpath, keyname, ca->sslpath, keyname, - ca->sslpath, oname, ca->passfile); - system(cmd); + switch (pid = vfork()) { + case -1: /* error */ + err(1, "vfork"); + + case 0: /* child */ + /* XXX pass in env is unsafe */ + if (asprintf(&envp[0], "EXPASS=%s", pass) <= 7) + exit(1); + envp[1] = NULL; + + argc = 0; + argv[argc++] = PATH_OPENSSL; + argv[argc++] = "pkcs12"; + argv[argc++] = "-export"; + argv[argc++] = "-name"; + argv[argc++] = keyname; + argv[argc++] = "-CAfile"; + if (asprintf(&argv[argc++], "%s/ca.crt", ca->sslpath) + <= 8) + exit(1); + argv[argc++] = "-inkey"; + if (asprintf(&argv[argc++], "%s/private/%s.key", + ca->sslpath, keyname) <= 15) + exit(1); + argv[argc++] = "-in"; + if (asprintf(&argv[argc++], "%s/%s.crt", ca->sslpath, + keyname) <= 7) + exit(1); + argv[argc++] = "-out"; + if (asprintf(&argv[argc++], "%s/private/%s.pfx", + ca->sslpath, oname) <= 15) + exit(1); + argv[argc++] = "-passout"; + argv[argc++] = "env:EXPASS"; + argv[argc++] = "-passin"; + if (asprintf(&argv[argc++], "file:%s", ca->passfile) + <= 6) + exit(1); + argv[argc++] = NULL; + + execve(PATH_OPENSSL, argv, envp); + exit(1); + } + if (waitpid(pid, &status, 0) < 0) + err(1, "waitpid"); + + if (status != 0) + errx(1, "export command failed: %s/private/%s.pfx", + ca->sslpath, oname); } - snprintf(cmd, sizeof(cmd), "env EXPASS=%s %s pkcs12 -export" - " -caname '%s' -name '%s' -cacerts -nokeys" - " -in %s/ca.crt -out %s/ca.pfx -passout env:EXPASS -passin file:%s", - pass, PATH_OPENSSL, ca->caname, ca->caname, ca->sslpath, - ca->sslpath, ca->passfile); - system(cmd); + + switch (pid = vfork()) { + case -1: /* error */ + err(1, "vfork"); + + case 0: /* child */ + /* XXX pass in env is unsafe */ + if (asprintf(&envp[0], "EXPASS=%s", pass) <= 7) + exit(1); + envp[1] = NULL; + + argc = 0; + argv[argc++] = PATH_OPENSSL; + argv[argc++] = "pkcs12"; + argv[argc++] = "-export"; + argv[argc++] = "-caname"; + argv[argc++] = ca->caname; + argv[argc++] = "-name"; + argv[argc++] = ca->caname; + argv[argc++] = "-cacerts"; + argv[argc++] = "-nokeys"; + argv[argc++] = "-in"; + if (asprintf(&argv[argc++], "%s/ca.crt", ca->sslpath) <= 8) + exit(1); + argv[argc++] = "-out"; + if (asprintf(&argv[argc++], "%s/ca.pfx", ca->sslpath) <= 8) + exit(1); + argv[argc++] = "-passout"; + argv[argc++] = "env:EXPASS"; + argv[argc++] = "-passin"; + if (asprintf(&argv[argc++], "file:%s", ca->passfile) <= 6) + exit(1); + argv[argc++] = NULL; + + execve(PATH_OPENSSL, argv, envp); + exit(1); + } + if (waitpid(pid, &status, 0) < 0) + err(1, "waitpid"); + + if (status != 0) + errx(1, "export command failed: %s/ca.pfx", ca->sslpath); + if ((p = mkdtemp(tpl)) == NULL) err(1, "could not create temp dir"); - chmod(p, 0755); + if (chmod(p, 0755) != 0) + err(1, "chmod"); - for (i = 0; i < nitems(hier); i++) { - strlcpy(dst, p, sizeof(dst)); - strlcat(dst, hier[i].dir, sizeof(dst)); - if (stat(dst, &st) != 0 && errno == ENOENT && - mkdir(dst, hier[i].mode) != 0) - err(1, "failed to create dir %s", dst); - } + ca_hier(p); /* create a file with the address of the peer to connect to */ if (myname != NULL) { - snprintf(dst, sizeof(dst), "%s/export/peer.txt", p); + xsnprintf(dst, sizeof(dst), "%s/export/peer.txt", p); + if ((fd = open(dst, O_WRONLY|O_CREAT, 0644)) == -1) err(1, "open %s", dst); write(fd, myname, strlen(myname)); close(fd); } - snprintf(src, sizeof(src), "%s/ca.pfx", ca->sslpath); - snprintf(dst, sizeof(dst), "%s/export/ca.pfx", p); - fcopy(src, dst, 0644); - - snprintf(src, sizeof(src), "%s/ca.crt", ca->sslpath); - snprintf(dst, sizeof(dst), "%s/ca/ca.crt", p); - fcopy(src, dst, 0644); + xsnprintf(src, sizeof(src), "%s/ca.pfx", ca->sslpath); + xsnprintf(dst, sizeof(dst), "%s/export/ca.pfx", p); + if (fcopy(src, dst, 0644) != 0) + errx(1, "fcopy: %s -> %s", src, dst); + + xsnprintf(src, sizeof(src), "%s/ca.crt", ca->sslpath); + xsnprintf(dst, sizeof(dst), "%s/ca/ca.crt", p); + if (fcopy(src, dst, 0644) != 0) + errx(1, "fcopy: %s -> %s", src, dst); - snprintf(src, sizeof(src), "%s/ca.crl", ca->sslpath); + xsnprintf(src, sizeof(src), "%s/ca.crl", ca->sslpath); if (stat(src, &st) == 0) { - snprintf(dst, sizeof(dst), "%s/crls/ca.crl", p); - fcopy(src, dst, 0644); + xsnprintf(dst, sizeof(dst), "%s/crls/ca.crl", p); + if (fcopy(src, dst, 0644) != 0) + errx(1, "fcopy: %s -> %s", src, dst); } if (keyname != NULL) { - snprintf(src, sizeof(src), "%s/private/%s.pfx", ca->sslpath, + xsnprintf(src, sizeof(src), "%s/private/%s.pfx", ca->sslpath, oname); - snprintf(dst, sizeof(dst), "%s/export/%s.pfx", p, oname); - fcopy(src, dst, 0644); + xsnprintf(dst, sizeof(dst), "%s/export/%s.pfx", p, oname); + if (fcopy(src, dst, 0644) != 0) + errx(1, "fcopy: %s -> %s", src, dst); - snprintf(src, sizeof(src), "%s/private/%s.key", ca->sslpath, + xsnprintf(src, sizeof(src), "%s/private/%s.key", ca->sslpath, keyname); - snprintf(dst, sizeof(dst), "%s/private/%s.key", p, keyname); - fcopy(src, dst, 0600); - snprintf(dst, sizeof(dst), "%s/private/local.key", p); - fcopy(src, dst, 0600); - - snprintf(src, sizeof(src), "%s/%s.crt", ca->sslpath, keyname); - snprintf(dst, sizeof(dst), "%s/certs/%s.crt", p, keyname); - fcopy(src, dst, 0644); - - snprintf(cmd, sizeof(cmd), "%s rsa -out %s/local.pub" - " -in %s/private/%s.key -pubout", PATH_OPENSSL, p, - ca->sslpath, keyname); - system(cmd); + xsnprintf(dst, sizeof(dst), "%s/private/%s.key", p, keyname); + if (fcopy(src, dst, 0600) != 0) + errx(1, "fcopy: %s -> %s", src, dst); + xsnprintf(dst, sizeof(dst), "%s/private/local.key", p); + if (fcopy(src, dst, 0600) != 0) + errx(1, "fcopy: %s -> %s", src, dst); + + xsnprintf(src, sizeof(src), "%s/%s.crt", ca->sslpath, keyname); + xsnprintf(dst, sizeof(dst), "%s/certs/%s.crt", p, keyname); + if (fcopy(src, dst, 0644) != 0) + errx(1, "fcopy: %s -> %s", src, dst); + + switch (pid = vfork()) { + case -1: /* error */ + err(1, "vfork"); + + case 0: /* child */ + argc = 0; + argv[argc++] = PATH_OPENSSL; + argv[argc++] = "rsa"; + argv[argc++] = "-out"; + if (asprintf(&argv[argc++], "%s/local.pub", p) <= 11) + exit(1); + argv[argc++] = "-in"; + if (asprintf(&argv[argc++], "%s/private/%s.key", + ca->sslpath, keyname) <= 15) + exit(1); + argv[argc++] = "-pubout"; + argv[argc++] = NULL; + + execv(PATH_OPENSSL, argv); + exit(1); + + } + if (waitpid(pid, &status, 0) < 0) + err(1, "waitpid"); + + if (status != 0) + errx(1, "export command failed: %s/local.pub", + ca->sslpath); } if (stat(PATH_TAR, &st) == 0) { - if (keyname == NULL) - snprintf(cmd, sizeof(cmd), "%s -zcf %s.tgz -C %s .", - PATH_TAR, oname, ca->sslpath); - else - snprintf(cmd, sizeof(cmd), "%s -zcf %s.tgz -C %s .", - PATH_TAR, oname, p); - system(cmd); - snprintf(src, sizeof(src), "%s.tgz", oname); + switch (pid= vfork()) { + case -1: /* error */ + err(1, "vfork"); + + case 0: /* child */ + argc = 0; + argv[argc++] = PATH_TAR; + argv[argc++] = "-zcf"; + if (asprintf(&argv[argc++], "%s.tgz", oname) <= 5) + exit(1); + argv[argc++] = "-C"; + if (keyname == NULL) + argv[argc++] = ca->sslpath; + else + argv[argc++] = p; + argv[argc++] = "."; + argv[argc++] = NULL; + + execv(PATH_TAR, argv); + exit(1); + } + if (waitpid(pid, &status, 0) < 0) + err(1, "waitpid"); + + if (status != 0) + errx(1, "export command failed: %s.tgz", oname); + + xsnprintf(src, sizeof(src), "%s.tgz", oname); if (realpath(src, dst) != NULL) printf("exported files in %s\n", dst); + else + err(1, "realpath: %s", src); } if (stat(PATH_ZIP, &st) == 0) { @@ -679,25 +826,47 @@ ca_export(struct ca *ca, char *keyname, if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) continue; - snprintf(src, sizeof(src), "%s/%s", EXPDIR, + xsnprintf(src, sizeof(src), "%s/%s", EXPDIR, de->d_name); - snprintf(dst, sizeof(dst), "%s/export/%s", p, + xsnprintf(dst, sizeof(dst), "%s/export/%s", p, de->d_name); - fcopy(src, dst, 644); + if (fcopy(src, dst, 0644) != 0) + errx(1, "fcopy: %s -> %s", src, dst); } closedir(dexp); } - snprintf(dst, sizeof(dst), "%s/export", p); + xsnprintf(dst, sizeof(dst), "%s/export", p); if (getcwd(src, sizeof(src)) == NULL) err(1, "could not get cwd"); if (chdir(dst) == -1) err(1, "could not change %s", dst); - snprintf(dst, sizeof(dst), "%s/%s.zip", src, oname); - snprintf(cmd, sizeof(cmd), "%s -qr %s .", PATH_ZIP, dst); - system(cmd); + switch (pid = vfork()) { + case -1: /* error */ + err(1, "vfork"); + + case 0: /* child */ + argc = 0; + argv[argc++] = PATH_ZIP; + argv[argc++] = "-qr"; + if (asprintf(&argv[argc++], "%s/%s.zip", src, oname) + <= 7) + exit(1); + argv[argc++] = "."; + argv[argc++] = NULL; + + execv(PATH_ZIP, argv); + exit(1); + } + if (waitpid(pid, &status, 0) < 0) + err(1, "waitpid"); + + if (status != 0) + errx(1, "export command failed: %s/%s.zip", src, oname); + + xsnprintf(dst, sizeof(dst), "%s/%s.zip", src, oname); printf("exported files in %s\n", dst); if (chdir(src) == -1)