[BACK]Return to subr_kubsan.c CVS log [TXT][DIR] Up to [local] / src / sys / kern

File: [local] / src / sys / kern / subr_kubsan.c (download)

Revision 1.12, Wed Nov 6 19:16:48 2019 UTC (4 years, 7 months ago) by anton
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, OPENBSD_7_3_BASE, OPENBSD_7_3, OPENBSD_7_2_BASE, OPENBSD_7_2, OPENBSD_7_1_BASE, OPENBSD_7_1, OPENBSD_7_0_BASE, OPENBSD_7_0, OPENBSD_6_9_BASE, OPENBSD_6_9, OPENBSD_6_8_BASE, OPENBSD_6_8, OPENBSD_6_7_BASE, OPENBSD_6_7, HEAD
Changes since 1.11: +6 -6 lines

Use atomic operations consistently while writing to kubsan_slot.
Otherwise, reports might go by unnoticed.

Prodded by and ok visa@

/*	$OpenBSD: subr_kubsan.c,v 1.12 2019/11/06 19:16:48 anton Exp $	*/

/*
 * Copyright (c) 2019 Anton Lindqvist <anton@openbsd.org>
 *
 * 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 <sys/types.h>
#include <sys/param.h>
#include <sys/atomic.h>
#include <sys/syslimits.h>
#include <sys/systm.h>
#include <sys/timeout.h>

#include <uvm/uvm_extern.h>

#define KUBSAN_INTERVAL	100	/* report interval in msec */
#define KUBSAN_NSLOTS	32

#define NUMBER_BUFSIZ		32
#define LOCATION_BUFSIZ		(PATH_MAX + 32)	/* filename:line:column */
#define LOCATION_REPORTED	(1U << 31)

#define NBITS(typ)	(1 << ((typ)->t_info >> 1))
#define SIGNED(typ)	((typ)->t_info & 1)

struct kubsan_report {
	enum {
		KUBSAN_FLOAT_CAST_OVERFLOW,
		KUBSAN_INVALID_VALUE,
		KUBSAN_NEGATE_OVERFLOW,
		KUBSAN_NONNULL_ARG,
		KUBSAN_OUT_OF_BOUNDS,
		KUBSAN_OVERFLOW,
		KUBSAN_POINTER_OVERFLOW,
		KUBSAN_SHIFT_OUT_OF_BOUNDS,
		KUBSAN_TYPE_MISMATCH,
		KUBSAN_UNREACHABLE,
	} kr_type;

	struct source_location *kr_src;

	union {
		struct {
			const struct float_cast_overflow_data *v_data;
			unsigned long v_val;
		} v_float_cast_overflow;

		struct {
			const struct invalid_value_data *v_data;
			unsigned long v_val;
		} v_invalid_value;

		struct {
			const struct overflow_data *v_data;
			unsigned int v_val;
		} v_negate_overflow;

		struct {
			const struct nonnull_arg_data *v_data;
		} v_nonnull_arg;

		struct {
			const struct out_of_bounds_data *v_data;
			unsigned int v_idx;
		} v_out_of_bounds;

		struct {
			const struct overflow_data *v_data;
			unsigned long v_lhs;
			unsigned long v_rhs;
			char v_op;
		} v_overflow;

		struct {
			const struct pointer_overflow_data *v_data;
			unsigned long v_base;
			unsigned long v_res;
		} v_pointer_overflow;

		struct {
			const struct shift_out_of_bounds_data *v_data;
			unsigned long v_lhs;
			unsigned long v_rhs;
		} v_shift_out_of_bounds;

		struct {
			const struct type_mismatch_data *v_data;
			unsigned long v_ptr;
		} v_type_mismatch;
	} kr_u;
};
#define kr_float_cast_overflow		kr_u.v_float_cast_overflow
#define kr_invalid_value		kr_u.v_invalid_value
#define kr_negate_overflow		kr_u.v_negate_overflow
#define kr_nonnull_arg			kr_u.v_nonnull_arg
#define kr_out_of_bounds		kr_u.v_out_of_bounds
#define kr_overflow			kr_u.v_overflow
#define kr_pointer_overflow		kr_u.v_pointer_overflow
#define kr_shift_out_of_bounds		kr_u.v_shift_out_of_bounds
#define kr_type_mismatch		kr_u.v_type_mismatch

struct type_descriptor {
	uint16_t t_kind;
	uint16_t t_info;
	char t_name[1];	/* type name as variable length array */
};

struct source_location {
	const char *sl_filename;
	uint32_t sl_line;
	uint32_t sl_column;
};

struct float_cast_overflow_data {
	struct source_location d_src;
	struct type_descriptor *d_ftype;	/* from type */
	struct type_descriptor *d_ttype;	/* to type */
};

struct invalid_value_data {
	struct source_location d_src;
	struct type_descriptor *d_type;
};

struct nonnull_arg_data {
	struct source_location d_src;
	struct source_location d_attr_src;	/* __attribute__ location */
	int d_idx;
};

struct out_of_bounds_data {
	struct source_location d_src;
	struct type_descriptor *d_atype;	/* array type */
	struct type_descriptor *d_itype;	/* index type */
};

struct overflow_data {
	struct source_location d_src;
	struct type_descriptor *d_type;
};

struct pointer_overflow_data {
	struct source_location d_src;
};

struct shift_out_of_bounds_data {
	struct source_location d_src;
	struct type_descriptor *d_ltype;
	struct type_descriptor *d_rtype;
};

struct type_mismatch_data {
	struct source_location d_src;
	struct type_descriptor *d_type;
	uint8_t d_align;	/* log2 alignment */
	uint8_t d_kind;
};

struct unreachable_data {
	struct source_location d_src;
};

int64_t		 kubsan_deserialize_int(struct type_descriptor *,
		    unsigned long);
uint64_t	 kubsan_deserialize_uint(struct type_descriptor *,
		    unsigned long);
void		 kubsan_defer_report(struct kubsan_report *);
void		 kubsan_format_int(struct type_descriptor *, unsigned long,
		    char *, size_t);
int		 kubsan_format_location(const struct source_location *, char *,
		    size_t);
int		 kubsan_is_reported(struct source_location *);
const char	*kubsan_kind(uint8_t);
void		 kubsan_report(void *);
void		 kubsan_unreport(struct source_location *);

static int	is_negative(struct type_descriptor *, unsigned long);
static int	is_shift_exponent_too_large(struct type_descriptor *,
		    unsigned long);

static const char	*pathstrip(const char *);

#ifdef KUBSAN_WATCH
int kubsan_watch = 2;
#else
int kubsan_watch = 1;
#endif

struct kubsan_report	*kubsan_reports = NULL;
struct timeout		 kubsan_timo = TIMEOUT_INITIALIZER(kubsan_report, NULL);
unsigned int		 kubsan_slot = 0;
int			 kubsan_cold = 1;

/*
 * Compiling the kernel with `-fsanitize=undefined' will cause the following
 * functions to be called when a sanitizer detects undefined behavior.
 * Some sanitizers are omitted since they are only applicable to C++.
 *
 * Every __ubsan_*() sanitizer function also has a corresponding
 * __ubsan_*_abort() function as part of the ABI provided by Clang.
 * But, since the kernel never is compiled with `fno-sanitize-recover' for
 * obvious reasons, they are also omitted.
 */

void
__ubsan_handle_add_overflow(struct overflow_data *data,
    unsigned long lhs, unsigned long rhs)
{
	struct kubsan_report kr = {
		.kr_type		= KUBSAN_OVERFLOW,
		.kr_src			= &data->d_src,
		.kr_overflow		= { data, lhs, rhs, '+' },
	};

	kubsan_defer_report(&kr);
}

void
__ubsan_handle_builtin_unreachable(struct unreachable_data *data)
{
	struct kubsan_report kr = {
		.kr_type		= KUBSAN_UNREACHABLE,
		.kr_src			= &data->d_src,
	};

	kubsan_defer_report(&kr);
}

void
__ubsan_handle_divrem_overflow(struct overflow_data *data,
    unsigned long lhs, unsigned long rhs)
{
	struct kubsan_report kr = {
		.kr_type		= KUBSAN_OVERFLOW,
		.kr_src			= &data->d_src,
		.kr_overflow		= { data, lhs, rhs, '/' },
	};

	kubsan_defer_report(&kr);
}

void
__ubsan_handle_float_cast_overflow(struct float_cast_overflow_data *data,
    unsigned long val)
{
	struct kubsan_report kr = {
		.kr_type		= KUBSAN_FLOAT_CAST_OVERFLOW,
		.kr_src			= &data->d_src,
		.kr_float_cast_overflow	= { data, val },
	};

	kubsan_defer_report(&kr);
}

void
__ubsan_handle_load_invalid_value(struct invalid_value_data *data,
    unsigned long val)
{
	struct kubsan_report kr = {
		.kr_type		= KUBSAN_INVALID_VALUE,
		.kr_src			= &data->d_src,
		.kr_invalid_value	= { data, val },
	};

	kubsan_defer_report(&kr);
}

void
__ubsan_handle_nonnull_arg(struct nonnull_arg_data *data)
{
	struct kubsan_report kr = {
		.kr_type		= KUBSAN_NONNULL_ARG,
		.kr_src			= &data->d_src,
		.kr_nonnull_arg		= { data },
	};

	kubsan_defer_report(&kr);
}

void
__ubsan_handle_mul_overflow(struct overflow_data *data,
    unsigned long lhs, unsigned long rhs)
{
	struct kubsan_report kr = {
		.kr_type		= KUBSAN_OVERFLOW,
		.kr_src			= &data->d_src,
		.kr_overflow		= { data, lhs, rhs, '*' },
	};

	kubsan_defer_report(&kr);
}

void
__ubsan_handle_negate_overflow(struct overflow_data *data, unsigned long val)
{
	struct kubsan_report kr = {
		.kr_type		= KUBSAN_NEGATE_OVERFLOW,
		.kr_src			= &data->d_src,
		.kr_negate_overflow	= { data, val },
	};

	kubsan_defer_report(&kr);
}

void
__ubsan_handle_out_of_bounds(struct out_of_bounds_data *data,
    unsigned long idx)
{
	struct kubsan_report kr = {
		.kr_type		= KUBSAN_OUT_OF_BOUNDS,
		.kr_src			= &data->d_src,
		.kr_out_of_bounds	= { data, idx },
	};

	kubsan_defer_report(&kr);
}

void
__ubsan_handle_pointer_overflow(struct pointer_overflow_data *data,
    unsigned long base, unsigned long res)
{
	struct kubsan_report kr = {
		.kr_type		= KUBSAN_POINTER_OVERFLOW,
		.kr_src			= &data->d_src,
		.kr_pointer_overflow	= { data, base, res },
	};

	kubsan_defer_report(&kr);
}

void
__ubsan_handle_shift_out_of_bounds(struct shift_out_of_bounds_data *data,
    unsigned long lhs, unsigned long rhs)
{
	struct kubsan_report kr = {
		.kr_type		= KUBSAN_SHIFT_OUT_OF_BOUNDS,
		.kr_src			= &data->d_src,
		.kr_shift_out_of_bounds	= { data, lhs, rhs },
	};

	kubsan_defer_report(&kr);
}

void
__ubsan_handle_sub_overflow(struct overflow_data *data,
    unsigned long lhs, unsigned long rhs)
{
	struct kubsan_report kr = {
		.kr_type		= KUBSAN_OVERFLOW,
		.kr_src			= &data->d_src,
		.kr_overflow		= { data, lhs, rhs, '-' },
	};

	kubsan_defer_report(&kr);
}

void
__ubsan_handle_type_mismatch_v1(struct type_mismatch_data *data,
    unsigned long ptr)
{
	struct kubsan_report kr = {
		.kr_type		= KUBSAN_TYPE_MISMATCH,
		.kr_src			= &data->d_src,
		.kr_type_mismatch	= { data, ptr },
	};

	kubsan_defer_report(&kr);
}

/*
 * Allocate storage for reports and schedule the reporter.
 * Must be called as early on as possible in order to catch undefined behavior
 * during boot.
 */
void
kubsan_init(void)
{
	kubsan_reports = (void *)uvm_pageboot_alloc(
	    sizeof(struct kubsan_report) * KUBSAN_NSLOTS);
	kubsan_cold = 0;

	timeout_add_msec(&kubsan_timo, KUBSAN_INTERVAL);
}

int64_t
kubsan_deserialize_int(struct type_descriptor *typ, unsigned long val)
{
	switch (NBITS(typ)) {
	case 8:
		return ((int8_t)val);
	case 16:
		return ((int16_t)val);
	case 32:
		return ((int32_t)val);
	case 64:
	default:
		return ((int64_t)val);
	}
}

uint64_t
kubsan_deserialize_uint(struct type_descriptor *typ, unsigned long val)
{
	switch (NBITS(typ)) {
	case 8:
		return ((uint8_t)val);
	case 16:
		return ((uint16_t)val);
	case 32:
		return ((uint32_t)val);
	case 64:
	default:
		return ((uint64_t)val);
	}
}

void
kubsan_defer_report(struct kubsan_report *kr)
{
	unsigned int slot;

	if (__predict_false(kubsan_cold == 1) ||
	    kubsan_is_reported(kr->kr_src))
		return;

	slot = atomic_inc_int_nv(&kubsan_slot) - 1;
	if (slot >= KUBSAN_NSLOTS) {
		/*
		 * No slots left, flag source location as not reported and
		 * hope a slot will be available next time.
		 */
		kubsan_unreport(kr->kr_src);
		return;
	}

	memcpy(&kubsan_reports[slot], kr, sizeof(*kr));
}

void
kubsan_format_int(struct type_descriptor *typ, unsigned long val,
    char *buf, size_t bufsiz)
{
	switch (typ->t_kind) {
	case 0:	/* integer */
		if (SIGNED(typ)) {
			int64_t i = kubsan_deserialize_int(typ, val);
			snprintf(buf, bufsiz, "%lld", i);
		} else {
			uint64_t u = kubsan_deserialize_uint(typ, val);
			snprintf(buf, bufsiz, "%llu", u);
		}
		break;
	default:
		snprintf(buf, bufsiz, "%#x<NaN>", typ->t_kind);
	}
}

int
kubsan_format_location(const struct source_location *src, char *buf,
    size_t bufsiz)
{
	const char *path;

	path = pathstrip(src->sl_filename);

	return snprintf(buf, bufsiz, "%s:%u:%u",
	    path, src->sl_line & ~LOCATION_REPORTED, src->sl_column);
}

int
kubsan_is_reported(struct source_location *src)
{
	uint32_t *line = &src->sl_line;
	uint32_t prev;

	/*
	 * Treat everything as reported when disabled.
	 * Otherwise, new violations would go by unnoticed.
	 */
	if (__predict_false(kubsan_watch == 0))
		return (1);

	do {
		prev = *line;
		/* If already reported, avoid redundant atomic operation. */
		if (prev & LOCATION_REPORTED)
			break;
	} while (atomic_cas_uint(line, prev, prev | LOCATION_REPORTED) != prev);

	return (prev & LOCATION_REPORTED);
}

const char *
kubsan_kind(uint8_t kind)
{
	static const char *kinds[] = {
		"load of",
		"store to",
		"reference binding to",
		"member access within",
		"member call on",
		"constructor call on",
		"downcast of",
		"downcast of",
		"upcast of",
		"cast to virtual base of",
		"_Nonnull binding to",
		"dynamic operation on"
	};

	if (kind >= nitems(kinds))
		return ("?");

	return (kinds[kind]);
}

void
kubsan_report(void *arg)
{
	char bloc[LOCATION_BUFSIZ];
	char blhs[NUMBER_BUFSIZ];
	char brhs[NUMBER_BUFSIZ];
	struct kubsan_report *kr;
	unsigned int nslots;
	unsigned int i = 0;

again:
	nslots = kubsan_slot;
	if (nslots == 0)
		goto done;
	if (nslots > KUBSAN_NSLOTS)
		nslots = KUBSAN_NSLOTS;

	for (; i < nslots; i++) {
		kr = &kubsan_reports[i];

		kubsan_format_location(kr->kr_src, bloc, sizeof(bloc));
		switch (kr->kr_type) {
		case KUBSAN_FLOAT_CAST_OVERFLOW: {
			const struct float_cast_overflow_data *data =
			    kr->kr_float_cast_overflow.v_data;

			kubsan_format_int(data->d_ftype,
			    kr->kr_float_cast_overflow.v_val,
			    blhs, sizeof(blhs));
			printf("kubsan: %s: %s of type %s is outside the range "
			    "of representable values of type %s\n",
			    bloc, blhs, data->d_ftype->t_name,
			    data->d_ttype->t_name);
			break;
		}

		case KUBSAN_INVALID_VALUE: {
			const struct invalid_value_data *data =
			    kr->kr_invalid_value.v_data;

			kubsan_format_int(data->d_type,
			    kr->kr_invalid_value.v_val, blhs, sizeof(blhs));
			printf("kubsan: %s: load invalid value: load of value " 
			    "%s is not a valid value for type %s\n",
			    bloc, blhs, data->d_type->t_name);
			break;
		}

		case KUBSAN_NEGATE_OVERFLOW: {
			const struct overflow_data *data =
			    kr->kr_negate_overflow.v_data;

			kubsan_format_int(data->d_type,
			    kr->kr_negate_overflow.v_val, blhs, sizeof(blhs));
			printf("kubsan: %s: negate overflow: negation of %s "
			    "cannot be represented in type %s\n",
			    bloc, blhs, data->d_type->t_name);
			break;
		}

		case KUBSAN_NONNULL_ARG: {
			const struct nonnull_arg_data *data =
			    kr->kr_nonnull_arg.v_data;

			if (data->d_attr_src.sl_filename)
				kubsan_format_location(&data->d_attr_src,
				    blhs, sizeof(blhs));
			else
				blhs[0] = '\0';

			printf("kubsan: %s: null pointer passed as argument "
			    "%d, which is declared to never be null%s%s\n",
			    bloc, data->d_idx,
			    blhs[0] ? "nonnull specified in " : "", blhs);
			break;
		}

		case KUBSAN_OUT_OF_BOUNDS: {
			const struct out_of_bounds_data *data =
			    kr->kr_out_of_bounds.v_data;

			kubsan_format_int(data->d_itype,
			    kr->kr_out_of_bounds.v_idx, blhs, sizeof(blhs));
			printf("kubsan: %s: out of bounds: index %s is out of " 
			    "range for type %s\n",
			    bloc, blhs, data->d_atype->t_name);
			break;
		}

		case KUBSAN_OVERFLOW: {
			const struct overflow_data *data =
			    kr->kr_overflow.v_data;

			kubsan_format_int(data->d_type,
			    kr->kr_overflow.v_lhs, blhs, sizeof(blhs));
			kubsan_format_int(data->d_type,
			    kr->kr_overflow.v_rhs, brhs, sizeof(brhs));
                        printf("kubsan: %s: %s integer overflow: %s %c %s "
                            "cannot be represented in type %s\n",
			    bloc, SIGNED(data->d_type) ? "signed" : "unsigned",
			    blhs, kr->kr_overflow.v_op, brhs,
			    data->d_type->t_name);
			break;
		}

		case KUBSAN_POINTER_OVERFLOW:
			printf("kubsan: %s: pointer overflow: pointer "
			    "expression with base %#lx overflowed to %#lx\n",
			    bloc, kr->kr_pointer_overflow.v_base,
			    kr->kr_pointer_overflow.v_res);
			break;

		case KUBSAN_SHIFT_OUT_OF_BOUNDS: {
			const struct shift_out_of_bounds_data *data =
				kr->kr_shift_out_of_bounds.v_data;
			unsigned long lhs = kr->kr_shift_out_of_bounds.v_lhs;
			unsigned long rhs = kr->kr_shift_out_of_bounds.v_rhs;

			kubsan_format_int(data->d_ltype, lhs, blhs,
			    sizeof(blhs));
			kubsan_format_int(data->d_rtype, rhs, brhs,
			    sizeof(brhs));
			if (is_negative(data->d_rtype, rhs))
				printf("kubsan: %s: shift: shift exponent %s "
				    "is negative\n",
				    bloc, brhs);
			else if (is_shift_exponent_too_large(data->d_rtype, rhs))
				printf("kubsan: %s: shift: shift exponent %s "
				    "is too large for %u-bit type\n",
				    bloc, brhs, NBITS(data->d_rtype));
			else if (is_negative(data->d_ltype, lhs))
				printf("kubsan: %s: shift: left shift of "
				    "negative value %s\n",
				    bloc, blhs);
			else
				printf("kubsan: %s: shift: left shift of %s by "
				    "%s places cannot be represented in type "
				    "%s\n",
				    bloc, blhs, brhs, data->d_ltype->t_name);
			break;
		}

		case KUBSAN_TYPE_MISMATCH: {
			const struct type_mismatch_data *data =
				kr->kr_type_mismatch.v_data;
			unsigned long ptr = kr->kr_type_mismatch.v_ptr;
			unsigned long align = 1UL << data->d_align;

			if (ptr == 0UL)
				printf("kubsan: %s: type mismatch: %s null "
				    "pointer of type %s\n",
				    bloc, kubsan_kind(data->d_kind),
				    data->d_type->t_name);
			else if (ptr & (align - 1))
				printf("kubsan: %s: type mismatch: %s "
				    "misaligned address %p for type %s which "
				    "requires %lu byte alignment\n",
				    bloc, kubsan_kind(data->d_kind),
				    (void *)ptr, data->d_type->t_name, align);
			else
				printf("kubsan: %s: type mismatch: %s address "
				    "%p with insufficient space for an object "
				    "of type %s\n",
				    bloc, kubsan_kind(data->d_kind),
				    (void *)ptr, data->d_type->t_name);
			break;
		}

		case KUBSAN_UNREACHABLE:
			printf("kubsan: %s: unreachable: calling "
			    "__builtin_unreachable()\n",
			    bloc);
			break;
		}

#ifdef DDB
		if (kubsan_watch == 2)
			db_enter();
#endif
	}

	/* New reports can arrive at any time. */
	if (atomic_cas_uint(&kubsan_slot, nslots, 0) != nslots) {
		if (nslots < KUBSAN_NSLOTS)
			goto again;
		atomic_swap_uint(&kubsan_slot, 0);
	}

done:
	timeout_add_msec(&kubsan_timo, KUBSAN_INTERVAL);
}

void
kubsan_unreport(struct source_location *src)
{
	uint32_t *line = &src->sl_line;

	atomic_clearbits_int(line, LOCATION_REPORTED);
}

static int
is_negative(struct type_descriptor *typ, unsigned long val)
{
	return (SIGNED(typ) && kubsan_deserialize_int(typ, val) < 0);
}

static int
is_shift_exponent_too_large(struct type_descriptor *typ, unsigned long val)
{
	return (kubsan_deserialize_int(typ, val) >= NBITS(typ));
}

/*
 * A source location is an absolute path making reports quite long.
 * Instead, use everything after the first /sys/ segment as the path.
 */
static const char *
pathstrip(const char *path)
{
	const char *needle = "/sys/";
	size_t i, j;

	for (i = j = 0; path[i] != '\0'; i++) {
		if (path[i] != needle[j]) {
			j = 0;
			continue;
		}

		if (needle[++j] == '\0')
			return path + i + 1;
	}

	return path;
}