Re: [PATCH v2 144/144] KVM: selftests: Sanity check input to ioctls() at build time

Sean Christopherson
2022-06-13T16:54:18Z

On Fri, Jun 10, 2022, Andrew Jones wrote:
> On Fri, Jun 03, 2022 at 12:43:31AM +0000, Sean Christopherson wrote:
> > Add a static assert to the KVM/VM/vCPU ioctl() helpers to verify that the
> > size of the argument provided matches the expected size of the IOCTL.
> > Because ioctl() ultimately takes a "void *", it's all too easy to pass in
> > garbage and not detect the error until runtime. E.g. while working on a
> > CPUID rework, selftests happily compiled when vcpu_set_cpuid()
> > unintentionally passed the cpuid() function as the parameter to ioctl()
> > (a local "cpuid" parameter was removed, but its use was not replaced with
> > "vcpu->cpuid" as intended).
> >
> > Tweak a variety of benign issues that aren't compatible with the sanity
> > check, e.g. passing a non-pointer for ioctls().
> >
> > Note, static_assert() requires a string on older versions of GCC. Feed
> > it an empty string to make the compiler happy.
> >
> > Signed-off-by: Sean Christopherson <seanjc@google.com>
> > ---
> > .../selftests/kvm/include/kvm_util_base.h | 61 +++++++++++++------
> > .../selftests/kvm/lib/aarch64/processor.c | 2 +-
> > tools/testing/selftests/kvm/lib/guest_modes.c | 2 +-
> > tools/testing/selftests/kvm/lib/kvm_util.c | 29 +--------
> > tools/testing/selftests/kvm/s390x/resets.c | 6 +-
> > .../selftests/kvm/x86_64/mmio_warning_test.c | 2 +-
> > .../kvm/x86_64/pmu_event_filter_test.c | 2 +-
> > .../selftests/kvm/x86_64/xen_shinfo_test.c | 6 +-
> > 8 files changed, 56 insertions(+), 54 deletions(-)
> >
> > diff --git a/tools/testing/selftests/kvm/include/kvm_util_base.h b/tools/testing/selftests/kvm/include/kvm_util_base.h
> > index 04ddab322b6b..0eaf0c9b7612 100644
> > --- a/tools/testing/selftests/kvm/include/kvm_util_base.h
> > +++ b/tools/testing/selftests/kvm/include/kvm_util_base.h
> > @@ -180,29 +180,56 @@ static inline bool kvm_has_cap(long cap)
> > #define __KVM_IOCTL_ERROR(_name, _ret) __KVM_SYSCALL_ERROR(_name, _ret)
> > #define KVM_IOCTL_ERROR(_ioctl, _ret) __KVM_IOCTL_ERROR(#_ioctl, _ret)
> >
> > -#define __kvm_ioctl(kvm_fd, cmd, arg) \
> > - ioctl(kvm_fd, cmd, arg)
> > +#define kvm_do_ioctl(fd, cmd, arg) \
> > +({ \
> > + static_assert(!_IOC_SIZE(cmd) || sizeof(*arg) == _IOC_SIZE(cmd), ""); \
> > + ioctl(fd, cmd, arg); \
> > +})
> >
> > -static inline void _kvm_ioctl(int kvm_fd, unsigned long cmd, const char *name,
> > - void *arg)
> > -{
> > - int ret = __kvm_ioctl(kvm_fd, cmd, arg);
> > +#define __kvm_ioctl(kvm_fd, cmd, arg) \
> > + kvm_do_ioctl(kvm_fd, cmd, arg)
> >
> > - TEST_ASSERT(!ret, __KVM_IOCTL_ERROR(name, ret));
> > -}
> > +
>
> While we've gained the static asserts we've also lost the type checking
> that the inline functions provided. Is there anyway we can bring them back
> with more macro tricks?

Gah, I overthought this. It doesn't even require macros, just a dummy helper.
I wasn't trying to use static_assert() to enforce the type check, which is how I
ended up with the sizeof() ugliness (not the one above). But it's far easier to
let the compiler do the checking.

I'll send a small fixup series to address this and your other feedback.

static __always_inline void static_assert_is_vm(struct kvm_vm *vm) { }

#define __vm_ioctl(vm, cmd, arg) \
({ \
static_assert_is_vm(vm); \
kvm_do_ioctl((vm)->fd, cmd, arg); \
})

static __always_inline void static_assert_is_vcpu(struct kvm_vcpu *vcpu) { }

#define __vcpu_ioctl(vcpu, cmd, arg) \
({ \
static_assert_is_vcpu(vcpu); \
kvm_do_ioctl((vcpu)->fd, cmd, arg); \
})

In file included from include/kvm_util.h:10,
from lib/x86_64/processor.c:9:
lib/x86_64/processor.c: In function ‘_vcpu_set_msr’:
lib/x86_64/processor.c:831:33: error: passing argument 1 of ‘static_assert_is_vcpu’ from incompatible pointer type [-Werror=incompatible-pointer-types]
831 | return __vcpu_ioctl(vcpu->vm, KVM_SET_MSRS, &buffer.header);
| ~~~~^~~~
| |
| struct kvm_vm *
include/kvm_util_base.h:232:31: note: in definition of macro ‘__vcpu_ioctl’
232 | static_assert_is_vcpu(vcpu); \
| ^~~~
include/kvm_util_base.h:225:68: note: expected ‘struct kvm_vcpu *’ but argument is of type ‘struct kvm_vm *’
225 | static __always_inline void static_assert_is_vcpu(struct kvm_vcpu *vcpu)
| ~~~~~~~~~~~~~~~~~^~~~
cc1: all warnings being treated as errors