Commit message: util: Check for NULL string in writefile

This check was there previously, but was removed in f549b757 with
the addition of a check during parse that every rule has rspfile
if and only if it has rspfile_content. However, this fails to
consider the possibility of those variables coming from the edge
or global environment. So, re-add the check.

Fixes #67.
CVE: CVE-2021-30218
CWE: CWE-284
Vulnerability description: samurai 1.2 has a NULL pointer dereference in writefile() in util.c via a crafted build file.

Project: samurai
File: util.c

bufadd(struct buffer *buf, char c)
{
	if (buf->len >= buf->cap) {
		buf->cap = buf->cap ? buf->cap * 2 : 1 << 8;
		buf->data = realloc(buf->data, buf->cap);
		if (!buf->data)
			fatal("realloc:");
	}
	buf->data[buf->len++] = c;
}

Project: samurai
File: util.c

xasprintf(char **s, const char *fmt, ...)
{
	va_list ap;
	int ret;
	size_t n;

	va_start(ap, fmt);
	ret = vsnprintf(NULL, 0, fmt, ap);
	va_end(ap);
	if (ret < 0)
		fatal("vsnprintf:");
	n = ret + 1;
	*s = xmalloc(n);
	va_start(ap, fmt);
	ret = vsnprintf(*s, n, fmt, ap);
	va_end(ap);
	if (ret < 0 || (size_t)ret >= n)
		fatal("vsnprintf:");

	return ret;
}

Project: samurai
File: util.c

xmalloc(size_t n)
{
	void *p;

	p = malloc(n);
	if (!p)
		fatal("malloc:");

	return p;
}

Project: samurai
File: util.c

warn(const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	vwarn(fmt, ap);
	va_end(ap);
}

Project: samurai
File: util.c

canonpath(struct string *path)
{
	char *component[60];
	int n;
	char *s, *d, *end;

	if (path->n == 0)
		fatal("empty path");
	s = d = path->s;
	end = path->s + path->n;
	n = 0;
	if (*s == '/') {
		++s;
		++d;
	}
	while (s < end) {
		switch (s[0]) {
		case '/':
			++s;
			continue;
		case '.':
			switch (s[1]) {
			case '\0': case '/':
				s += 2;
				continue;
			case '.':
				if (s[2] != '/' && s[2] != '\0')
					break;
				if (n > 0) {
					d = component[--n];
				} else {
					*d++ = s[0];
					*d++ = s[1];
					*d++ = s[2];
				}
				s += 3;
				continue;
			}
		}
		if (n == LEN(component))
			fatal("path has too many components: %s", path->s);
		component[n++] = d;
		while (*s != '/' && *s != '\0')
			*d++ = *s++;
		*d++ = *s++;
	}
	if (d == path->s) {
		*d++ = '.';
		*d = '\0';
	} else {
		*--d = '\0';
	}
	path->n = d - path->s;
}

Project: samurai
File: util.c

xreallocarray(void *p, size_t n, size_t m)
{
	p = reallocarray(p, n, m);
	if (!p)
		fatal("reallocarray:");

	return p;
}

Project: samurai
File: util.c

makedirs(struct string *path, bool parent)
{
	int ret;
	struct stat st;
	char *s, *end;

	ret = 0;
	end = path->s + path->n;
	for (s = end - parent; s > path->s; --s) {
		if (*s != '/' && *s)
			continue;
		*s = '\0';
		if (stat(path->s, &st) == 0)
			break;
		if (errno != ENOENT) {
			warn("stat %s:", path->s);
			ret = -1;
			break;
		}
	}
	if (s > path->s && s < end)
		*s = '/';
	while (++s <= end - parent) {
		if (*s != '\0')
			continue;
		if (ret == 0 && mkdir(path->s, 0777) < 0 && errno != EEXIST) {
			warn("mkdir %s:", path->s);
			ret = -1;
		}
		if (s < end)
			*s = '/';
	}

	return ret;
}

Project: samurai
File: util.c

fatal(const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	vwarn(fmt, ap);
	va_end(ap);
	exit(1);
}

Project: samurai
File: util.c

xmemdup(const char *s, size_t n)
{
	char *p;

	p = xmalloc(n);
	memcpy(p, s, n);

	return p;
}

Project: samurai
File: util.c

mkstr(size_t n)
{
	struct string *str;

	str = xmalloc(sizeof(*str) + n + 1);
	str->n = n;

	return str;
}

Project: samurai
File: util.c

delevalstr(void *ptr)
{
	struct evalstring *str = ptr;
	struct evalstringpart *p, *next;

	if (!str)
		return;
	for (p = str->parts; p; p = next) {
		next = p->next;
		if (p->var)
			free(p->var);
		else
			free(p->str);
		free(p);
	}
	free(str);
}

Project: samurai
File: util.c

writefile(const char *name, struct string *s)
{
	FILE *f;
	int ret;

	f = fopen(name, "w");
	if (!f) {
		warn("open %s:", name);
		return -1;
	}
	ret = 0;
	if (s && (fwrite(s->s, 1, s->n, f) != s->n || fflush(f) != 0)) {
		warn("write %s:", name);
		ret = -1;
	}
	fclose(f);

	return ret;
}

Project: samurai
File: util.c

reallocarray(void *p, size_t n, size_t m)
{
	if (m && n > SIZE_MAX / m) {
		errno = ENOMEM;
		return NULL;
	}
	return realloc(p, n * m);
}

Project: samurai
File: util.c

vwarn(const char *fmt, va_list ap)
{
	fprintf(stderr, "%s: ", argv0);
	vfprintf(stderr, fmt, ap);
	if (fmt[0] && fmt[strlen(fmt) - 1] == ':') {
		putc(' ', stderr);
		perror(NULL);
	} else {
		putc('\n', stderr);
	}
}