2

I'm using the following command to run a QEMU VM:

qemu-system-x86_64 -enable-kvm -m 1024 -smp 2 -hda disk.qcow2

By default, the guest OS has internet access, but can also access open ports on the host OS. How can I prevent the guest OS from accessing the host ports (but without limiting its internet access)?

2 Answers 2

3

This looks somewhat problematic if you do not have experience of advanced iptables manipulation and overview of how Linux filters traffic for local processes.

In your mode qemu runs an emulated NAT: all calls to NIC from guest will be translated as socket/connect/send/recv calls by qemu process itself. This means that connections are made by machine itself, from 127.0.0.1. At this point you can run qemu as another user, and filter that user by adding an owner match:

iptables -I OUTPUT -o lo -m owner --uid-owner username -m multiport --dports ports -j DROP

where username is a name of user you want to filter and ports is a comma separated list of ports you wish to disable for that machine. To run qemu as another user, you need to run it via tools like sudo or logging in as the user with su or login.

Without this, you end up to filter yourself, so if you will add a general rule to filter ports, you will be blocked from accessing these ports as well.

Another way is to change the way qemu does networking. A good way to filter traffic well is to bind qemu to virtual ethernet device:

  • Enable packet forwarding.

  • Install tunctl, add virtual network interface which owner is you:

tunctl -u yourname -t qemu

(remember to add this command to something like rc.local to make it permanent)

  • Configure qemu interface (use ip / ifconfig or other OS provided tool) to assign a free /24 subnet to it. This subnet is needed to be set in your guest OS as well. Then run qemu with -net tap,ifname=qemu,script=off. Configure guest OS networking again.

Then you can easily filter guest OS traffic which is represented by qemu virtual interface:

iptables -I FORWARD -i qemu -m multiport --dports ports -j DROP

should work.

But the NAT stopped working. If you need to make NAT work again, you should add a rule that will patch IP addresses going out of your machine. If you have eth0 interface where your all traffic goes, you enable NAT for it:

iptables -t nat -I POSTROUTING -o eth0 -j MASQUERADE
1
  • Perhaps they also need to enable packet forwarding in the kernel for NAT via iptables? Unless it has already been set of course. However, the QEMU "NAT" does not need it I believe so perhaps it has not.
    – Ned64
    May 1, 2021 at 21:35
0

I'd like to add that there's a bit of a simpler firewall rule if you are using default (-netdev user) networking.

iptables -I OUTPUT -o lo -m owner --uid-owner qemu-user -d 127.0.0.1 -j DROP

Note that you must run qemu under the user qemu-user, which you can do with sudo -u qemu-user qemu-system-x86_64 -enable-kvm ......

This blocks all ports of the host from being visible on the guest without having to specify them (like in the first answer). Note that the reason the destination address is 127.0.0.1 and not 10.0.2.2 (the address at which the guest sees the host by default) is because of the way qemu performs NAT.

If you're interested here's some detail on how qemu deals with default networking:

Qemu intercepts the TCP and UDP connections done by the raw link-layer packets flying out of the virtualized network card and translates them to corresponding connections performed by the qemu process itself, via calls to socket(), connect(), sendto(), recvfrom()... While doing this it also performs some address translation, so connections to 10.0.2.2 and 10.0.2.3 get translated to connections to 127.0.0.1 and 127.0.0.53 respectively. This is why the OUTPUT chain rule drops qemu's connections to 127.0.0.1, making the host invisible to the guest except for the DNS server in the host (and the emulated DHCP server inside qemu, but this is all done internally in qemu).

Here you can see the networking related syscalls performed by qemu when the guest resolves a dns domain with 10.0.2.3 (nslookup google.com 10.0.2.3):

$ sudo strace -f -e trace=network -p <qemu_pid>
strace: Process 14529 attached with 10 threads
[pid 14529] socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC, IPPROTO_IP) = 99
[pid 14529] sendto(99, "\236\353\1\0\0\1\0\0\0\0\0\0\6google\3com\0\0\1\0\1", 28, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("127.0.0.53")}, 16) = 28
[pid 14529] recvfrom(99, "\236\353\201\200\0\1\0\1\0\0\0\0\6google\3com\0\0\1\0\1\300\f\0\1"..., 1500, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("127.0.0.53")}, [128->16]) = 44
[pid 14529] socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC, IPPROTO_IP) = 100
[pid 14529] sendto(100, "@R\1\0\0\1\0\0\0\0\0\0\6google\3com\0\0\34\0\1", 28, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("127.0.0.53")}, 16) = 28
[pid 14529] recvfrom(100, "@R\201\200\0\1\0\1\0\0\0\0\6google\3com\0\0\34\0\1\300\f\0\34"..., 1500, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("127.0.0.53")}, [128->16]) = 56

As you can see, 10.0.2.3 gets translated to 127.0.0.53. Qemu only allows UDP traffic to port 53 when the guest talks to this address, so the host is not further exposed, except for its dns server daemon.

2
  • This may not work in usermode QEMU because the virtual machine / the QEMU emulator does not run under a UID other than the user starting it?
    – Ned64
    May 1, 2021 at 21:37
  • @Ned64 you can run qemu usermode under a different user the same way you can run a qemu full-system vm, using sudo -u. Qemu usermode just passes all the syscalls of the emulated userspace binary to the host kernel, it doesn't emulate a whole system, this solution is for qemu-system, not usermode.
    – mmm
    May 1, 2021 at 22:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.