From: Rusty Russell <rusty@rustcorp.com.au>
To: qemu-devel@nongnu.org
Subject: [Qemu-devel] mini-HOWTO: Using qemu to lock down tetrinetx server
Date: Fri, 19 Sep 2003 17:15:43 +1000 [thread overview]
Message-ID: <20030919081826.E04DA2C003@lists.samba.org> (raw)
[-- Attachment #1: Type: text/plain, Size: 323 bytes --]
Hi all,
I recently set up a tetrinet server inside a qemu image, for a
layer of additional security. The image is under 2MB, and below is a
mini-howto in setting up such a server in case anyone else is
interested.
You will need:
1) A qemu-compatible disk image: I have a simple script to make one,
(see attachment).
[-- Attachment #2: script to make qemu root --]
[-- Type: text/plain, Size: 2114 bytes --]
#! /bin/sh
# Script to make image of given size (in MB)
if [ $# -ne 2 ]; then
echo Usage: "$0 size-in-MB image" >&2
echo "eg $0 150 qemu-disk" >&2
exit 1
fi
SIZE=$1
IMAGE=$2
HEADS=16
SECTORS=63
# 512 bytes in a sector: cancel the 512 with one of the 1024s...
CYLINDERS=$(( $SIZE * 1024 * 2 / ($HEADS * $SECTORS) ))
# Create a filesystem: one track for partition table.
dd bs=$(($SECTORS * 512)) if=/dev/zero of=$IMAGE.raw count=$(($CYLINDERS * $HEADS - 1))
mke2fs -q -m1 -F -j $IMAGE.raw
# Prepend partiation table
# Create file with partition table.
uudecode -o- << "EOF" | gunzip > $IMAGE
begin 664 partition-table.gz
M'XL("*_<##\"`W!A<G1I=&EO;BUT86)L90#LT#$-`"`0!,&']D6A`D6XP1T&
M"%B@))FIMKGF(OA9C;%;EENYZO.Z3P\"````!P``__\:!0````#__QH%````
M`/__&@4`````__\:!0````#__QH%`````/__&@4`````__\:!0````#__QH%
M`````/__&@4`````__\:!0````#__QH%`````/__&@4`````__\:!0````#_
M_QH%`````/__&@4`````__\:!0````#__QH%`````/__&@4`````__\:!0``
M``#__QH%`````/__&@4`````__\:!0````#__QH%`````/__&@4`````__\:
M!0````#__QH%`````/__&@4`````__\:!0````#__QH%`````/__&@4`````
M__\:!0````#__QH%`````/__&@4`````__\:!0````#__QH%`````/__&@4`
M````__\:!0````#__QH%`````/__&@4`````__\:!0````#__QH%`````/__
M&@4`````__\:!0````#__QH%`````/__&@4`````__\:!0````#__QH%````
M`/__&@4`````__\:!0````#__QH%`````/__&@4`````__\:!0````#__QH%
M`````/__&@4`````__\:!0````#__QH%`````/__&@4`````__\:!0````#_
M_QH%`````/__&@4`````__\:!0````#__QH%`````/__&@4`````__\:!0``
M``#__QH%`````/__&@4`````__\:!0````#__QH%`````/__&@4`````__\:
M!0````#__QH%`````/__&@4`````__\:!0````#__QH%`````/__&@4`````
M__\:!0````#__QH%`````/__&@4`````__\:!0````#__QH%`````/__&@4`
M````__\:!0````#__QH%`````/__&@4`````__\:!0````#__QH%`````/__
M&@4`````__\:!0````#__QH%`````/__&@4`````__\:!0````#__QH%````
M`/__&@4`````__\:!0````#__QH%`````/__&@4`````__\:!0````#__QH%
M`````/__&@4`````__\:!0````#__QH%`````/__&@4`````__\:!0````#_
M_QH%`````/__&@4`````__\:!0````#__QH%`````/__0@``````__\#`%&_
&<90`?@``
`
end
EOF
cat $IMAGE.raw >> $IMAGE
rm $IMAGE.raw
# Repartition so one partition covers entire disk.
echo '63,' | sfdisk -uS -H$HEADS -S$SECTORS -C$CYLINDERS $IMAGE
[-- Attachment #3: Type: text/plain, Size: 286 bytes --]
2) The "vl" binary, and a qemu-compatible kernel bzImage.
I used qemu 0.4.3, modified with the -tun-fd patch and the closed
stdin patch. If you use the softmm version of qemu, any kernel
bzImage should work.
3) The root-running version of the tundev program (see attachment)
[-- Attachment #4: tundev C source: root-running version --]
[-- Type: text/plain, Size: 1675 bytes --]
#define _GNU_SOURCE /* asprintf */
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <net/if.h>
#include <linux/if_tun.h>
/* Tiny code to open tap/tun device, and hand the fd to vl.
Run as root, drops to given user. */
int main(int argc, char *argv[])
{
struct ifreq ifr;
struct passwd *p;
unsigned int i;
char *newargs[argc];
int fd;
if (argc < 4) {
fprintf(stderr,
"Usage: switch user logfile vl <vl options>...\n");
exit(1);
}
fd = open("/dev/net/tun", O_RDWR);
if (fd < 0) {
perror("Could not open /dev/net/tun");
exit(1);
}
memset(&ifr, 0, sizeof(ifr));
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
strncpy(ifr.ifr_name, "tun%d", IFNAMSIZ);
if (ioctl(fd, TUNSETIFF, (void *) &ifr) != 0) {
perror("Could not get tun device");
exit(1);
}
/* Set userid. */
p = getpwnam(argv[1]);
if (!p) {
fprintf(stderr, "No user '%s'\n", argv[1]);
exit(1);
}
setgroups(0, NULL);
setgid(p->pw_gid);
if (setuid(p->pw_uid) != 0) {
perror("setting uid");
exit(1);
}
/* Insert -tun-fd, delete other args */
newargs[0] = argv[3];
asprintf(&newargs[1], "-tun-fd=%d", fd);
for (i = 4; i <= argc; i++)
newargs[i-2] = argv[i];
if (strcmp(argv[2], "-") == 0) {
execvp(newargs[0], newargs);
exit(1);
}
switch (fork()) {
case 0: {
close(1);
close(2);
open(argv[2], O_WRONLY|O_APPEND);
open(argv[2], O_WRONLY|O_APPEND);
close(0);
execvp(newargs[0], newargs);
exit(1);
}
case -1:
perror("fork failed");
exit(1);
}
printf("%s\n", ifr.ifr_name);
exit(0);
}
[-- Attachment #5: Type: text/plain, Size: 436 bytes --]
Steps:
1) Mount the filesystem. If you've already prepended a partition
table, use
losetup -o $((63 * 512)) /dev/loop0 <FILE>
mount /dev/loop0 /mnt/qemu
Otherwise you can just do:
mount -o loop <FILE> /mnt/qemu
2) Remove unneccessary files, add files you need, and lock down
directories. This is best done by creating an oldroot dir and
moving things into that. For tetrinet, I ended up under 2MB
(see attachment)
[-- Attachment #6: ls-R of qemu image with tetrinet server --]
[-- Type: text/plain, Size: 3044 bytes --]
/mnt/qemu:
total 222
dr-x--x--x 2 root root 1024 Sep 17 21:10 dev
dr-x--x--x 3 root root 1024 Sep 17 20:48 etc
-rwx------ 1 root root 53516 Jun 28 12:09 ifconfig
d--x--xr-x 2 root root 1024 Sep 17 21:08 lib
drwx------ 2 root root 12288 Sep 18 13:30 lost+found
-rwx------ 1 root root 41848 Jun 28 12:09 route
-rwxrwxr-x 1 rusty rusty 6748 Sep 18 13:27 run
-rwx--x--- 1 root games 104184 Jan 25 2003 tetrinetx
dr-x--x--x 3 root root 1024 Sep 17 20:54 var
/mnt/qemu/dev:
total 0
crw------- 1 root root 5, 1 Jul 10 19:32 console
crw-rw---- 1 root games 4, 64 Sep 16 18:48 ttyS0
/mnt/qemu/etc:
total 6
-rw-r----- 1 root games 41 Sep 17 20:48 hosts
-rw-r----- 1 root games 465 Mar 12 1999 nsswitch.conf
-rw-r----- 1 root games 2030 Jun 16 03:40 protocols
-rw-r----- 1 root games 64 Jul 10 10:49 resolv.conf
dr-x--x--x 2 root root 1024 Sep 17 20:34 tetrinetx
/mnt/qemu/etc/tetrinetx:
total 7
-rw-r----- 1 root games 4042 Sep 17 20:34 game.conf
-rw-r----- 1 root games 87 Jan 25 2003 game.motd
-rw-r----- 1 root games 180 Jan 25 2003 game.pmotd
-rw-r----- 1 root games 768 Jan 25 2003 game.secure
/mnt/qemu/lib:
total 1574
-rwxr-xr-x 1 root root 82456 Apr 20 04:57 ld-2.3.1.so
lrwxrwxrwx 1 root root 11 Jul 10 19:32 ld-linux.so.2 -> ld-2.3.1.so
lrwxr-xr-x 1 root root 14 Sep 16 17:27 libadns.so.1 -> libadns.so.1.0
-rwxr-xr-x 1 root root 61044 Sep 9 2002 libadns.so.1.0
-rwxr-xr-x 1 root root 1103880 Apr 20 04:57 libc-2.3.1.so
lrwxrwxrwx 1 root root 13 Jul 10 19:32 libc.so.6 -> libc-2.3.1.so
-rwxr-xr-x 1 root root 7992 Apr 20 04:57 libdl-2.3.1.so
lrwxrwxrwx 1 root root 14 Jul 10 19:32 libdl.so.2 -> libdl-2.3.1.so
lrwxrwxrwx 1 root root 17 Jul 10 19:32 libncurses.so.5 -> libncurses.so.5.3
-rwxr-xr-x 1 root root 238160 Jun 15 02:13 libncurses.so.5.3
-rwxr-xr-x 1 root root 12828 Apr 20 04:57 libnss_dns-2.3.1.so
lrwxrwxrwx 1 root root 19 Jul 10 19:32 libnss_dns.so.2 -> libnss_dns-2.3.1.so
-rwxr-xr-x 1 root root 32204 Apr 20 04:57 libnss_files-2.3.1.so
lrwxrwxrwx 1 root root 21 Jul 10 19:32 libnss_files.so.2 -> libnss_files-2.3.1.so
-rwxr-xr-x 1 root root 56652 Apr 20 04:57 libresolv-2.3.1.so
lrwxrwxrwx 1 root root 18 Jul 10 19:32 libresolv.so.2 -> libresolv-2.3.1.so
/mnt/qemu/lost+found:
total 0
/mnt/qemu/var:
total 1
dr-x--x--x 3 root root 1024 Sep 17 20:54 log
/mnt/qemu/var/log:
total 1
dr-x--x--x 2 root root 1024 Sep 17 20:54 tetrinetx
/mnt/qemu/var/log/tetrinetx:
total 0
lrwxrwxrwx 1 root root 10 Sep 17 20:54 game.log -> /dev/ttyS0
[-- Attachment #7: Type: text/plain, Size: 342 bytes --]
3) I wanted the filesystem to be read-only (too bad about the tetrinet
high scores file: I don't care), but I wanted the logs, so I made
/var/log/tetrinetx/game.log a symlink to /dev/ttyS0, and allowed
the games group to write to it.
4) I wrote a simply launcher program called run to avoid the need for
a shell (see attachment)
[-- Attachment #8: run.c source for tetrinet server --]
[-- Type: text/plain, Size: 1570 bytes --]
/* Simply C program to act as init. */
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <grp.h>
#include <sys/reboot.h>
#include <linux/reboot.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
static int wait_for(const char *prog)
{
int status;
wait(&status);
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
fprintf(stderr, "run: %s failed\n", prog);
return 0;
}
return 1;
}
int main(int argc, char *argv[])
{
if (fork() == 0) {
execl("/ifconfig", "ifconfig", "eth0", "192.168.1.2", NULL);
fprintf(stderr, "run: ifconfig exec failed\n");
exit(1);
}
if (!wait_for("ifconfig"))
goto reboot;
if (fork() == 0) {
execl("/route", "route", "add", "default", "gw", "192.168.1.1",
NULL);
fprintf(stderr, "run: route exec failed\n");
exit(1);
}
if (!wait_for("route"))
goto reboot;
/* tetrinet does DNS lookup on own hostname */
sethostname("ozlabs.org", strlen("ozlabs.org"));
if (fork() == 0) {
setgroups(0, NULL);
/* Games/games */
setgid(60);
setuid(5);
close(0);
close(1);
close(2);
open("/dev/null", O_RDWR);
open("/dev/null", O_RDWR);
open("/dev/null", O_RDWR);
execl("/tetrinetx", "tetrinetx", NULL);
exit(1);
}
if (!wait_for("tetrinetx"))
goto reboot;
/* Drop privs, why not? */
setgroups(0, NULL);
setgid(65534);
setuid(65534);
close(0);
close(1);
close(2);
while (wait(NULL) >= 0);
reboot:
sync();
reboot(LINUX_REBOOT_CMD_RESTART);
sleep(60);
return 1;
}
[-- Attachment #9: Type: text/plain, Size: 1185 bytes --]
5) Unmount the qemu image. Once I'd trimmed out most of the code, I
created a new smaller one one, mounted that, and used tar to
transfer everything across.
6) My startup script looks like so:
#! /bin/sh
# Set up forwarding and masquerading, and make sure tun module is loaded.
set -e
INTERFACE=`./tundev games /var/log/qemu-tetrinet ./vl -hda tetrinet-filesystem bzImage root=/dev/hda1 ide1=noprobe ide2=noprobe ide3=noprobe ide4=noprobe ide5=noprobe init=/run`
ifdown $INTERFACE 2>/dev/null || true
ifup $INTERFACE
You need to reconfigure the tun devices everytime they get opened,
hence the ifdown/ifup stuff.
8) You will also need to forward incoming connections to the server.
In this case, the server makes no outgoing connections, so this
works:
iptables -t nat -A PREROUTING -p tcp -i eth0 --dport 31457 -j DNAT --to 192.168.1.2
iptables -A FORWARD -o tun0 -m state --state NEW,INVALID -j DROP
iptables -N LOGDROP
iptables -A LOGDROP -j LOG
iptables -A LOGDROP -j DROP
iptables -A FORWARD -i tun0 -m state --state NEW,INVALID -j LOGDROP
Good luck!
Rusty.
--
Anyone who quotes me in their sig is an idiot. -- Rusty Russell.
next reply other threads:[~2003-09-19 8:18 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2003-09-19 7:15 Rusty Russell [this message]
2003-09-19 20:25 ` [Qemu-devel] mini-HOWTO: Using qemu to lock down tetrinetx server Fabrice Bellard
2003-09-19 20:38 ` Herbert Poetzl
2003-09-20 3:16 ` Rusty Russell
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20030919081826.E04DA2C003@lists.samba.org \
--to=rusty@rustcorp.com.au \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.