[OpenWrt-Devel] [PATCH procd 3/4] system: sysupgrade: rework firmware validation

Hauke Mehrtens hauke at hauke-m.de
Sat Jan 4 14:41:44 EST 2020


On 1/3/20 1:46 AM, Petr Štetiar wrote:
> Fixes following deficiencies:
> 
>  * unhandled read() errors
>  * everything bundled in one long function, which is hard to follow and
>    reason about
>  * JSON parser errors are being ignored, anything else then
>    json_tokener_continue is fatal error
>  * JSON parser errors are being output to stderr, thus invisible via SSH
>  * validate_firmware_image_call can fail at a lot of places, but we just
>    get one generic "Firmware image couldn't be validated" so it's hard
>    to debug
> 
> Cc: Rafał Miłecki <rafal at milecki.pl>
> Signed-off-by: Petr Štetiar <ynezz at true.cz>
> ---
>  system.c | 170 ++++++++++++++++++++++++++++++++++++++++---------------
>  1 file changed, 123 insertions(+), 47 deletions(-)
> 
> diff --git a/system.c b/system.c
> index 5cd88e0d8227..f0198a5b20b8 100644
> --- a/system.c
> +++ b/system.c
> @@ -37,6 +37,12 @@ static struct blob_buf b;
>  static int notify;
>  static struct ubus_context *_ctx;
>  
> +enum vjson_state {
> +	VJSON_ERROR,
> +	VJSON_CONTINUE,
> +	VJSON_SUCCESS,
> +};
> +
>  static int system_board(struct ubus_context *ctx, struct ubus_object *obj,
>                   struct ubus_request_data *req, const char *method,
>                   struct blob_attr *msg)
> @@ -413,30 +419,127 @@ static int proc_signal(struct ubus_context *ctx, struct ubus_object *obj,
>  	return 0;
>  }
>  
> +static enum vjson_state vjson_error(char **b, const char *fmt, ...)

Please annotate the function with:
__attribute__ ((format (printf, 2, 3)));

> +{
> +	static char buf[256] = { 0 };
> +	const char *pfx = "Firmware image couldn't be validated: ";
> +	va_list va;
> +	int r;
> +
> +	r = snprintf(buf, sizeof(buf), "%s", pfx);
> +	if (r < 0) {
> +		*b = "vjson_error() snprintf failed";
> +		return VJSON_ERROR;
> +	}
> +
> +	va_start(va, fmt);
> +	r = vsnprintf(buf+r, sizeof(buf)-r, fmt, va);
Please check here for truncation:
rv = vsnprintf(buf+r, sizeof(buf)-r, fmt, va);
if (rv < 0 || rv >=  sizeof(buf)-r ) {


> +	if (r < 0) {
> +		*b = "vjson_error() vsnprintf failed";
> +		return VJSON_ERROR;
> +	}
> +	va_end(va);
> +
> +	*b = buf;
> +	return VJSON_ERROR;
> +}
> +
> +static enum vjson_state vjson_parse_token(json_tokener *tok, char *buf, ssize_t len, char **err)
> +{
> +	json_object *jsobj = NULL;
> +
> +	jsobj = json_tokener_parse_ex(tok, buf, len);
> +	if (json_tokener_get_error(tok) == json_tokener_continue)
> +		return VJSON_CONTINUE;
> +
> +	if (json_tokener_get_error(tok) == json_tokener_success) {
> +		if (json_object_get_type(jsobj) != json_type_object) {
> +			json_object_put(jsobj);
> +			return vjson_error(err, "result is not an JSON object");
> +		}
> +
> +		blobmsg_add_object(&b, jsobj);
> +		json_object_put(jsobj);
> +		return VJSON_SUCCESS;
> +	}
> +
> +	return vjson_error(err, "failed to parse JSON: %s (%d)",
> +			   json_tokener_error_desc(json_tokener_get_error(tok)),
> +			   json_tokener_get_error(tok));

Why don't you free it here too json_object_put()?

> +}
> +
> +static enum vjson_state vjson_parse(int fd, char **err)
> +{
> +	enum vjson_state r = VJSON_ERROR;
> +	size_t read_count = 0;
> +	char buf[64] = { 0 };
> +	json_tokener *tok;
> +	ssize_t len;
> +	int _errno;
> +
> +	tok = json_tokener_new();
> +	if (!tok)
> +		return vjson_error(err, "json_tokener_new() failed");
> +
> +	vjson_error(err, "incomplete JSON input");
> +
> +	while ((len = read(fd, buf, sizeof(buf)))) {
> +		if (len < 0 && errno == EINTR)
> +			continue;
> +
> +		if (len < 0) {
> +			_errno = errno;
> +			json_tokener_free(tok);
> +			return vjson_error(err, "read() failed: %s (%d)",
> +					   strerror(_errno), _errno);
> +		}
> +
> +		read_count += len;
> +		r = vjson_parse_token(tok, buf, len, err);
> +		if (r != VJSON_CONTINUE)
> +			break;
> +
> +		memset(buf, 0, sizeof(buf));
> +	}
> +
> +	if (read_count == 0)
> +		vjson_error(err, "no JSON input");
> +
> +	json_tokener_free(tok);
> +	return r;
> +}
> +
>  /**
>   * validate_firmware_image_call - perform validation & store result in global b
>   *
>   * @file: firmware image path
>   */
> -static int validate_firmware_image_call(const char *file)
> +static enum vjson_state validate_firmware_image_call(const char *file, char **err)
>  {
>  	const char *path = "/usr/libexec/validate_firmware_image";
> -	json_object *jsobj = NULL;
> -	json_tokener *tok;
> -	char buf[64];
> -	ssize_t len;
> +	enum vjson_state ret = VJSON_ERROR;
> +	int _errno;
>  	int fds[2];
> -	int err;
>  	int fd;
>  
> -	if (pipe(fds))
> -		return -errno;
> +	blob_buf_init(&b, 0);
> +	vjson_error(err, "unhandled error");
In which case is this returned, aren't there specific error handlers in
call cases now and vjson_parse() would overwrite it again?

> +
> +	if (pipe(fds)) {
> +		_errno = errno;
> +		return vjson_error(err, "pipe() failed: %s (%d)",
> +				   strerror(_errno), _errno);
> +	}
>  
>  	switch (fork()) {
>  	case -1:
> +		_errno = errno;
> +
>  		close(fds[0]);
>  		close(fds[1]);
> -		return -errno;
> +
> +		return vjson_error(err, "fork() failed: %s (%d)",
> +				   strerror(_errno), _errno);
>  	case 0:
>  		/* Set stdin & stderr to /dev/null */
>  		fd = open("/dev/null", O_RDWR);
> @@ -458,43 +561,10 @@ static int validate_firmware_image_call(const char *file)
>  	/* Parent process */
>  	close(fds[1]);
>  
> -	tok = json_tokener_new();
> -	if (!tok) {
> -		close(fds[0]);
> -		return -ENOMEM;
> -	}
> -
> -	blob_buf_init(&b, 0);
> -	while ((len = read(fds[0], buf, sizeof(buf)))) {
> -		if (len < 0 && errno == EINTR)
> -			continue;
> -
> -		jsobj = json_tokener_parse_ex(tok, buf, len);
> -
> -		if (json_tokener_get_error(tok) == json_tokener_success)
> -			break;
> -		else if (json_tokener_get_error(tok) == json_tokener_continue)
> -			continue;
> -		else
> -			fprintf(stderr, "Failed to parse JSON: %d\n",
> -				json_tokener_get_error(tok));
> -	}
> -
> +	ret = vjson_parse(fds[0], err);
>  	close(fds[0]);
>  
> -	err = -ENOENT;
> -	if (jsobj) {
> -		if (json_object_get_type(jsobj) == json_type_object) {
> -			blobmsg_add_object(&b, jsobj);
> -			err = 0;
> -		}
> -
> -		json_object_put(jsobj);
> -	}
> -
> -	json_tokener_free(tok);
> -
> -	return err;
> +	return ret;
>  }
>  
>  enum {
> @@ -512,6 +582,8 @@ static int validate_firmware_image(struct ubus_context *ctx,
>  				   const char *method, struct blob_attr *msg)
>  {
>  	struct blob_attr *tb[__VALIDATE_FIRMWARE_IMAGE_MAX];
> +	enum vjson_state ret = VJSON_ERROR;
> +	char *err;
>  
>  	if (!msg)
>  		return UBUS_STATUS_INVALID_ARGUMENT;
> @@ -520,7 +592,8 @@ static int validate_firmware_image(struct ubus_context *ctx,
>  	if (!tb[VALIDATE_FIRMWARE_IMAGE_PATH])
>  		return UBUS_STATUS_INVALID_ARGUMENT;
>  
> -	if (validate_firmware_image_call(blobmsg_get_string(tb[VALIDATE_FIRMWARE_IMAGE_PATH])))
> +	ret = validate_firmware_image_call(blobmsg_get_string(tb[VALIDATE_FIRMWARE_IMAGE_PATH]), &err);
> +	if (ret != VJSON_SUCCESS)
>  		return UBUS_STATUS_UNKNOWN_ERROR;
>  
>  	ubus_send_reply(ctx, req, b.head);
> @@ -580,6 +653,8 @@ static int sysupgrade(struct ubus_context *ctx, struct ubus_object *obj,
>  	struct blob_attr *validation[__VALIDATION_MAX];
>  	struct blob_attr *tb[__SYSUPGRADE_MAX];
>  	bool valid, forceable, allow_backup;
> +	enum vjson_state ret = VJSON_ERROR;
> +	char *err;
>  
>  	if (!msg)
>  		return UBUS_STATUS_INVALID_ARGUMENT;
> @@ -588,8 +663,9 @@ static int sysupgrade(struct ubus_context *ctx, struct ubus_object *obj,
>  	if (!tb[SYSUPGRADE_PATH] || !tb[SYSUPGRADE_PREFIX])
>  		return UBUS_STATUS_INVALID_ARGUMENT;
>  
> -	if (validate_firmware_image_call(blobmsg_get_string(tb[SYSUPGRADE_PATH]))) {
> -		sysupgrade_error(ctx, req, "Firmware image couldn't be validated");
> +	ret = validate_firmware_image_call(blobmsg_get_string(tb[SYSUPGRADE_PATH]), &err);
> +	if (ret != VJSON_SUCCESS) {
> +		sysupgrade_error(ctx, req, err);
>  		return UBUS_STATUS_UNKNOWN_ERROR;
>  	}
>  
> 
> _______________________________________________
> openwrt-devel mailing list
> openwrt-devel at lists.openwrt.org
> https://lists.openwrt.org/mailman/listinfo/openwrt-devel
> 


-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 488 bytes
Desc: OpenPGP digital signature
URL: <http://lists.infradead.org/pipermail/openwrt-devel/attachments/20200104/f4a5897b/attachment.sig>
-------------- next part --------------
_______________________________________________
openwrt-devel mailing list
openwrt-devel at lists.openwrt.org
https://lists.openwrt.org/mailman/listinfo/openwrt-devel


More information about the openwrt-devel mailing list