Mouse and Keyboard passthrough to Windows HVM

I am trying to pass a keyboard and mouse to a windows hvm (or any hvm) via libvirt xml config. For reference, I do not want to use a USB kvm or use the USB proxy. I do not want the hvm to have control of the usb device. I want to pass this mouse/keyboard inputs via software only. I cannot seem to find a lot of information about passing mouse/keyboard in Xen or qubes specifically. Could the stubdom-linux may be getting in the way?

I have added a devices block to extend my hvm’s xml libvirt config via: Custom libvirt config — core-admin v4.2.5-0-g5b51295e-dirty documentation. The windows domain will start but will not show any new devices.

When an hvm is being displayed in qubes, how are the tablet and keyboard commands passed to it? I would love to be pointed to more documentation on that and the linux stubdomain in general.

I appreciate the support! Please

Can you explain what you want to do, that isn’t already there by default? When you start a HVM, it gets emulated mouse (or rather tablet) and keyboard connected, that sends it input events when you interact with that HVM’s window. It isn’t your real full USB device. So, it sounds like exactly what you want, no?

Technically, qemu running in a stubdomain has built in GUI agent that shows just a single window (the one you see) using our GUI protocol.

I appreciate the fast reply. I want to quickly switch my keyboard and mouse between dom0 and the windows hvm. If i used a linux hvm, I can accomplish this using the input-proxy running from dom0 to the linux hvm to pass a keyboard and mouse. I guess I am curious why a custom libvirt xml for my windows hvm does not actually pass anything windows can see?

Also could I use the emulated mouse and keyboard without creating the VGA or gui agent window?

Edit: I also do not want to pass my usb keyboard/mouse to an untrusted domain and would prefer to just send inputs.

I should have given more context because there may be a solution I am not thinking about.

I have a windows hvm that has an nvidia gpu passed to it. The GPU, xen disk/network drivers are all working. Installing Qubes core agent in windows causes problems. I have read that some users can pass usb to the vm via a usb qube but it seemed not as stable. I also worry that virtually hot plugging a mouse and keyboard from the usb qube to windows and back will just cause issues. I do not need (or on the security side want) windows to have usb control over the mouse and keyboard. I only need the input sent to it.

I have the kind of input system that I want on a Debian HVM by doing the following:

I am using this project (GitHub - mciupak/libvirt-evdev: Libvirt evdev input service) to split my keyboard and mouse in dom0 (could be in a usb qube though) into essentially two devices each: . This lets me have a host and guest keyboard and the program lets me switch input between them.

Then I can use input-proxy-sender and receiver on the guest keyboard/mouse to pass the inputs to the Debian HVM. (GitHub - QubesOS/qubes-app-linux-input-proxy).

This seems to work very well. I wanted to create ability to switch between the gaming gpu enabled windows 10 and dom0 just like how in the KVM hypervisor you can switch control between host and guest. In KVM’s case it uses an evdev device tag in the libvirt XML. Xen does not have access to this: libvirt: Domain XML format. I do not need this to be built into Xen. I can use the libvirt-evdev program to do the split and switching for me but I cant seem to figure out how to do passthrough of a mouse and keyboard in Xen. In my search for this I cannot find any libvirt or xl config that demonstrates a keyboard/mouse passthrough. I have tried appending this libvirt config without any luck:

  <input type='passthrough' bus='virtio'>
    <source evdev='/dev/input/by-id/keyboard'/>
  </input>

I hope that makes more sense what I am aiming for. Thanks so much!

I see, so you can’t use the standard input integration because your VM doesn’t have normal VGA (and even if it would, its resolution, and screen location doesn’t match your actual video output through attached GPU.

The way you are trying to do it with libvirt xml won’t work, because it controls qemu that is running inside stubdomain, not dom0. So, it doesn’t have access to whatever /dev/input/* you point it at. It’s probably possible to add input proxy to the stubdomain to transfer input events there, and then attach that to qemu (via libvirt xml), but that will require some tinkering. If you want to try anyway, the stubdomain sources are here: GitHub - QubesOS/qubes-vmm-xen-stubdom-linux. The “full” variant already has qrexec (which is used for USB passthrough), so the missing piece would be “just” input proxy.

Technically, attaching USB keyboard/mouse directly would be simpler to accomplish (if you enable stubdom-qrexec feature using qvm-features tool), but as you correctly identified, it comes with exposing your USB devices to potential attack from that VM.

Unfortunately I don’t see any simple option that would just work and meet your requirements.

I appreciate you breaking that down for me. I will look into the full stubdomain as you have noted.

Is there a way to interact with the stubdomain so I can prototype things easily? Or somewhere else I can read up more about the Linux stub domain?

I have made progress in this matter but am stuck on the final part for a working prototype. I modified the package vmm-xen-stubdom-linux in Qubes builder to add uinput support:

Added the a file in linux/config/

CONFIG_INPUT=y
CONFIG_INPUT_MISC=y
CONFIG_INPUT_UINPUT=y

The program input-proxy-reciever should be compiled at this stage into the rootfs but I have just manually added it via:

See Neowutran’s gpu guide or gpu passthrough with 3.5GB issue

mkdir stubroot
cp /usr/libexec/xen/boot/qemu-stubdom-linux-rootfs stubroot/qemu-stubdom-linux-rootfs.gz
cd stubroot
gunzip qemu-stubdom-linux-rootfs.gz
cpio -i -d -H newc --no-absolute-filenames < qemu-stubdom-linux-rootfs
rm qemu-stubdom-linux-rootfs

(ADD input-proxy-reciever to /bin)

This works and I have tested via running this command in dom0 (note -dm to access the stubdomain):

sudo qrexec-client -d gpu_hvm-dm -l 'input-proxy-sender /dev/input/by-id/mykeyboard' root:'input-proxy-reciever'

Using qrexec-client to pass commands to the stubdomain I can see that the uinput device is being created.

I am now stuck as I cannot run input-proxy-reciever before qemu starts since the vchan connection or socket isn’t established yet. It looks like in the init script for the stubdomain there is a qemu socket you could connect to. I tried finding how to connect to it from dom0 but cannot find any information. Is there a way to add a device while qemu is running?

If there is not a way to add an input device while its running then I will need to use something like libvirt-evdev to create a dummy uinput device for qemu to grab then later forward commands to it. This seems unnecessary to me though.

Thank you!

Update: uinput is working but an evdev or event device isn’t being created in /dev. I am not sure what is needed for that to be created.

Could you please provide information on how to modify the package vmm-xen-stubdom-linux in Qubes builder;
Also do you know if it’s possible to provide input to the stubdomain through sys-usb?

Have you checked if you compiled the stubdom kernel with CONFIG_INPUT_EVDEV enabled?

I’m stuck in the “attach that to qemu (via libvirt xml)” part, could you please give an example, when I tried using input type passtrough in libvirt as the example above it still says it’s missing the evdev path despite /dev/input/device0 being present in stubdom (I’ve created a dummy to be used until I run: sudo qrexec-client -d example-dm -l 'input-proxy-sender /dev/input/device*' root:'input-proxy-receiver --keyboard')

I am running this all from dom0 only because that is where my keyboard is attached, but the input proxy was designed to run from sys-usb. Ideally I would use sys-usb.

I modified the package but am only copying the rootfs to dom0. I followed the qubes builder page, had it download some sources so I didn’t have to build everything, and then ran make vmm-xen-stubdom-linux. I rebuilt it with CONFIG_INPUT_EVDEV and I do see the correct event device in /dev/input now. Let me know if you have more questions about this part, I can post what I did exactly.

How and at what time during the stub domain boot up are you creating the dummy device? I do not see how to call any qrexec commands before qemu has started in the stub domain. I imagine qemu is trying to use that dummy input before you have created it.

Sorry for taking so long to reply, I was hoping I would figure it out and than tell you the good news however that’s not the case.

The way I was creating a dummy device wasn’t the proper way of doing it as it was not a valid input, device I was just creating a file named event0 in /dev/input.

To use sys-usb we can just send the input to dom0 and use input-proxy-sender the same way.

Something that I’d like to try but I don’t know how to do is to create a valid dummy device so that qemu picks it up and then redirect the “real” input to the dummy

So far I’ve added the following to the rootfs init file:

evdev_args=$‘-object\ninput-linux,id=kbd1,evdev=/dev/input/event0,grab_all=on,repeat=on’

and $evdev_args to qemu just like $dm_args.

But since you can’t call qrexec commands before qemu starts; It won’t boot unless it gets a valid input device that’s why I’d like to create a fake input that receives the real input if that makes any sense (like a symlink).

I just realized you mentioned forwarding the input that’s exactly what I was trying to say have you made any progress in that?

@khmartinsen any updates on this?

I have found a solution that works but definitely needs to be cleaned up. The performance is great though. For example my use of mkfifo and cat feels very hacky and I imagine there is a better way using file descriptors (like in the init file). Anyways, here is the full setup:

0. Proceed with caution!

Make sure to make a backup copy of qemu-stubdom-linux-full-kernel and qemu-stubdom-linux-full-rootfs in /usr/libexec/xen/boot/ so you can always go back to a working system.

I do not know about the security concerns of this setup.


1. Modifying the stub domain kernel

Using Qubes builder I remade qemu-stubdom-linux-full-kernel by adding a file I called 60-input:

CONFIG_INPUT=y
CONFIG_INPUT_MISC=y
CONFIG_INPUT_UINPUT=y
CONFIG_INPUT_EVDEV=y

Then copy the kernel to /usr/libexec/xen/boot/


2. Modifying the stub domain rootfs

As stated above follow Neowutran’s gpu guide. For this you don’t need to do the “gpu_” modification but its the same steps for editing the init file. Be sure to edit the full version of stubdomain-linux-full for the kernel and rootfs

mkdir stubroot
cp /usr/libexec/xen/boot/qemu-stubdom-linux-full-rootfs stubroot/qemu-stubdom-linux-full-rootfs.gz
cd stubroot
gunzip qemu-stubdom-linux-full-rootfs.gz
cpio -i -d -H newc --no-absolute-filenames < qemu-stubdom-linux-full-rootfs
rm qemu-stubdom-linux-full-rootfs

edit init just after the usb_args= section to have:

# Setup input proxy devices
mkfifo /tmp/proxykeyboard
mkfifo /tmp/proxymouse

{ cat <>/tmp/proxykeyboard & } | USER=root input-proxy-receiver --keyboard &

USER=root cat /bin/kbhello.txt > /tmp/proxykeyboard &

{ cat <>/tmp/proxymouse & } | USER=root input-proxy-receiver --mouse &

USER=root cat /bin/mhello.txt > /tmp/proxymouse &

evdev_args=$'-object\ninput-linux,id=kbd1,evdev=/dev/input/event1,grab_all=on,repeat=on,\n-object\ninput-linux,id=mou1,evdev=/dev/input/event0,'

Note: You may have to swap event0 and event1 depending on how creates the devices. One could probably use the --name feature for input-proxy-receiver to find the correct event id.

Add $evdev_args to the qemu command at the end of init so it looks like:

qemu -sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \
-chardev pipe,path=/tmp/qmp/qemu,id=m -mon chardev=m,mode=control \
-chardev socket,server,nowait,path=/tmp/qemu.qmp,id=m2 -mon chardev=m2,mode=control \
    $dm_args $evdev_args $pa_args $usb_args $kernel &

Input-proxy-reciever needs some info before it creates the uinput device. We need to pass it to it via kbhello.txt and mhello.txt. You can name this whatever you want. To create it run this in dom0 or wherever your keyboard/mouse is located. Immediately stop the program running before moving your mouse or pressing any other keys via CTRL+C:

input-proxy-sender /dev/input/by-id/mykeyboard > kbhello.txt

Repeat for a mouse. Place these files in rootfs/bin or somewhere else.

Copy input-proxy-reciever to the bin folder in stubroot (I just copied this from dom0’s bin. This should probably be compiled in step 1 though).

Then finally put it back together and replace the original rootfs:

find . -print0 | cpio --null -ov \
--format=newc | gzip -9 > ../qemu-stubdom-linux-full-rootfs
sudo mv ../qemu-stubdom-linux-full-rootfs /usr/libexec/xen/boot/

3. Pass a mouse and keyboard to the stub domain
A mouse and keyboard can be passed to the stub domain of the HVM you want via this script. My hvm is called gpu_win10 and it’s stub domain is then gpu_win10-dm.

#!/bin/bash
qrexec-client -d gpu_win10-dm -l 'input-proxy-sender /dev/input/by-id/guest-mouse' root:'cat | cat > /tmp/proxymouse' &
qrexec-client -d gpu_win10-dm -l 'input-proxy-sender /dev/input/by-id/guest-keyboard' root:'cat | cat > /tmp/proxykeyboard' &

My mouse and keyboard are called guest-mouse and guest-keyboard because I am using GitHub - mciupak/libvirt-evdev: Libvirt evdev input service to split my keyboard into two devices: a host and guest device. By passing just the guest to gpu_win10-dm I can now switch back and forth between dom0 and gpu_win10 when I press my scroll lock key.


4. Final Steps and Notes

By default the qemu-stubdom-linux-full-* versions will not be used. In my setup I have my windows hvm qvm-features set to:

audio-model ich6 (this definitely forces use of full)
stubdom-qrexec 1 (this should force usage of full)
gui-emulated 0
gui 0
video-model none

By setting this I also disable input from qubes_drv which does not place nicely with this setup. (see comments below)

I also experience an issue where if too many keys are pressed while in guest mode, the extra keys are sent to the host keyboard which then dom0 reads.

Thank you for the help @h5409j. I will try to reply faster to any issues anyone has or errors I have made.

4 Likes

@khmartinsen Thank you for taking the time to write a reply explaining how you did it it’s really helpful, but unfortunately I wasn’t able to follow it. I think I’m messing up in the edit init just after the usb_args= seaction to have: part.

Is it supposed to look like this? (assuming I only want to pass the keyboard)

usb_args=
if test -e /bin/qrexec-agent; then
    usb_args=$'-device\nnec-usb-xhci,id=xhci'
    mkdir -p /var/run/qubes
    USER=root qrexec-agent &
    touch /dev/mdev.log
    mdev -d
fi

mkfifo /tmp/proxykeyboard
{ cat <>/tmp/proxykeyboard & } | USER=root input-proxy-receiver --keyboard &
USER=root cat /bin/kbhello.txt > /tmp/proxykeyboard &
evdev_args=$'-object\ninput-linux,id=kbd1,evdev=/dev/input/event0,grab_all=on,repeat=on'

The reason I’m asking is because I still can’t run any qrexec-client commands before it tries to pick the input device to use, making it unable to boot

I forgot a major part and I will go back and add it to that post in a bit.

This line: USER=root cat /bin/kbhello.txt > /tmp/proxykeyboard & is referring to creating a file called kbhello.txt from running this command in dom0. Be sure to to type anything and then exiting the command. It may need sudo.

input-proxy-sender /dev/input/by-id/mykeyboard > kbhello.txt

Put this in bin or another folder in the rootfs.

Once you have all this setup you start your hvm then run the script in step 3.

1 Like

Thank you so much it works!

1 Like

@khmartinsen one last thing, do you happen to know what causes the mouse to move to the top left whenever I right click or left click?