aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Maydell <peter.maydell@linaro.org>2022-02-23 09:25:05 +0000
committerPeter Maydell <peter.maydell@linaro.org>2022-02-23 09:25:05 +0000
commit4aa2e497a98bafe962e72997f67a369e4b52d9c1 (patch)
treea688eb9c357022c35eab15ad1dc155b413d581e8
parent31e3caf21b6cdf54d11f3744b8b341f07a30b5d7 (diff)
parent2720ceda0521bc43139cfdf45e3e470559e11ce3 (diff)
downloadqemu-4aa2e497a98bafe962e72997f67a369e4b52d9c1.zip
qemu-4aa2e497a98bafe962e72997f67a369e4b52d9c1.tar.gz
qemu-4aa2e497a98bafe962e72997f67a369e4b52d9c1.tar.bz2
Merge remote-tracking branch 'remotes/berrange-gitlab/tags/misc-next-pull-request' into staging
This misc series of changes: - Improves documentation of SSH fingerprint checking - Fixes SHA256 fingerprints with non-blockdev usage - Blocks the clone3, setns, unshare & execveat syscalls with seccomp - Blocks process spawning via clone syscall, but allows threads, with seccomp - Takes over seccomp maintainer role - Expands firmware descriptor spec to allow flash without NVRAM # gpg: Signature made Thu 17 Feb 2022 11:57:13 GMT # gpg: using RSA key DAF3A6FDB26B62912D0E8E3FBE86EBB415104FDF # gpg: Good signature from "Daniel P. Berrange <dan@berrange.com>" [full] # gpg: aka "Daniel P. Berrange <berrange@redhat.com>" [full] # Primary key fingerprint: DAF3 A6FD B26B 6291 2D0E 8E3F BE86 EBB4 1510 4FDF * remotes/berrange-gitlab/tags/misc-next-pull-request: docs: expand firmware descriptor to allow flash without NVRAM MAINTAINERS: take over seccomp from Eduardo Otubo seccomp: block setns, unshare and execveat syscalls seccomp: block use of clone3 syscall seccomp: fix blocking of process spawning seccomp: add unit test for seccomp filtering seccomp: allow action to be customized per syscall block: print the server key type and fingerprint on failure block: support sha256 fingerprint with pre-blockdev options block: better document SSH host key fingerprint checking Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
-rw-r--r--MAINTAINERS5
-rw-r--r--block/ssh.c42
-rw-r--r--docs/interop/firmware.json54
-rw-r--r--docs/system/qemu-block-drivers.rst.inc30
-rw-r--r--softmmu/qemu-seccomp.c282
-rw-r--r--tests/unit/meson.build4
-rw-r--r--tests/unit/test-seccomp.c269
7 files changed, 599 insertions, 87 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index c3b5003..37c0110 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2990,10 +2990,11 @@ F: docs/sphinx/fakedbusdoc.py
F: tests/qtest/dbus*
Seccomp
-M: Eduardo Otubo <otubo@redhat.com>
-S: Supported
+M: Daniel P. Berrange <berrange@redhat.com>
+S: Odd Fixes
F: softmmu/qemu-seccomp.c
F: include/sysemu/seccomp.h
+F: tests/unit/test-seccomp.c
Cryptography
M: Daniel P. Berrange <berrange@redhat.com>
diff --git a/block/ssh.c b/block/ssh.c
index 3b5bf34..a64db14 100644
--- a/block/ssh.c
+++ b/block/ssh.c
@@ -386,14 +386,28 @@ static int compare_fingerprint(const unsigned char *fingerprint, size_t len,
return *host_key_check - '\0';
}
+static char *format_fingerprint(const unsigned char *fingerprint, size_t len)
+{
+ static const char *hex = "0123456789abcdef";
+ char *ret = g_new0(char, (len * 2) + 1);
+ for (size_t i = 0; i < len; i++) {
+ ret[i * 2] = hex[((fingerprint[i] >> 4) & 0xf)];
+ ret[(i * 2) + 1] = hex[(fingerprint[i] & 0xf)];
+ }
+ ret[len * 2] = '\0';
+ return ret;
+}
+
static int
check_host_key_hash(BDRVSSHState *s, const char *hash,
- enum ssh_publickey_hash_type type, Error **errp)
+ enum ssh_publickey_hash_type type, const char *typestr,
+ Error **errp)
{
int r;
ssh_key pubkey;
unsigned char *server_hash;
size_t server_hash_len;
+ const char *keytype;
r = ssh_get_server_publickey(s->session, &pubkey);
if (r != SSH_OK) {
@@ -401,6 +415,8 @@ check_host_key_hash(BDRVSSHState *s, const char *hash,
return -EINVAL;
}
+ keytype = ssh_key_type_to_char(ssh_key_type(pubkey));
+
r = ssh_get_publickey_hash(pubkey, type, &server_hash, &server_hash_len);
ssh_key_free(pubkey);
if (r != 0) {
@@ -410,12 +426,16 @@ check_host_key_hash(BDRVSSHState *s, const char *hash,
}
r = compare_fingerprint(server_hash, server_hash_len, hash);
- ssh_clean_pubkey_hash(&server_hash);
if (r != 0) {
- error_setg(errp, "remote host key does not match host_key_check '%s'",
- hash);
+ g_autofree char *server_fp = format_fingerprint(server_hash,
+ server_hash_len);
+ error_setg(errp, "remote host %s key fingerprint '%s:%s' "
+ "does not match host_key_check '%s:%s'",
+ keytype, typestr, server_fp, typestr, hash);
+ ssh_clean_pubkey_hash(&server_hash);
return -EPERM;
}
+ ssh_clean_pubkey_hash(&server_hash);
return 0;
}
@@ -436,13 +456,16 @@ static int check_host_key(BDRVSSHState *s, SshHostKeyCheck *hkc, Error **errp)
case SSH_HOST_KEY_CHECK_MODE_HASH:
if (hkc->u.hash.type == SSH_HOST_KEY_CHECK_HASH_TYPE_MD5) {
return check_host_key_hash(s, hkc->u.hash.hash,
- SSH_PUBLICKEY_HASH_MD5, errp);
+ SSH_PUBLICKEY_HASH_MD5, "md5",
+ errp);
} else if (hkc->u.hash.type == SSH_HOST_KEY_CHECK_HASH_TYPE_SHA1) {
return check_host_key_hash(s, hkc->u.hash.hash,
- SSH_PUBLICKEY_HASH_SHA1, errp);
+ SSH_PUBLICKEY_HASH_SHA1, "sha1",
+ errp);
} else if (hkc->u.hash.type == SSH_HOST_KEY_CHECK_HASH_TYPE_SHA256) {
return check_host_key_hash(s, hkc->u.hash.hash,
- SSH_PUBLICKEY_HASH_SHA256, errp);
+ SSH_PUBLICKEY_HASH_SHA256, "sha256",
+ errp);
}
g_assert_not_reached();
break;
@@ -556,6 +579,11 @@ static bool ssh_process_legacy_options(QDict *output_opts,
qdict_put_str(output_opts, "host-key-check.type", "sha1");
qdict_put_str(output_opts, "host-key-check.hash",
&host_key_check[5]);
+ } else if (strncmp(host_key_check, "sha256:", 7) == 0) {
+ qdict_put_str(output_opts, "host-key-check.mode", "hash");
+ qdict_put_str(output_opts, "host-key-check.type", "sha256");
+ qdict_put_str(output_opts, "host-key-check.hash",
+ &host_key_check[7]);
} else if (strcmp(host_key_check, "yes") == 0) {
qdict_put_str(output_opts, "host-key-check.mode", "known_hosts");
} else {
diff --git a/docs/interop/firmware.json b/docs/interop/firmware.json
index 8d8b0be..4e049b1 100644
--- a/docs/interop/firmware.json
+++ b/docs/interop/firmware.json
@@ -210,24 +210,61 @@
'data' : { 'filename' : 'str',
'format' : 'BlockdevDriver' } }
+
+##
+# @FirmwareFlashType:
+#
+# Describes how the firmware build handles code versus variable
+# persistence.
+#
+# @split: the executable file contains code while the NVRAM
+# template provides variable storage. The executable
+# must be configured read-only and can be shared between
+# multiple guests. The NVRAM template must be cloned
+# for each new guest and configured read-write.
+#
+# @combined: the executable file contains both code and
+# variable storage. The executable must be cloned
+# for each new guest and configured read-write.
+# No NVRAM template will be specified.
+#
+# @stateless: the executable file contains code and variable
+# storage is not persisted. The executable must
+# be configured read-only and can be shared
+# between multiple guests. No NVRAM template
+# will be specified.
+#
+# Since: 7.0.0
+##
+{ 'enum': 'FirmwareFlashMode',
+ 'data': [ 'split', 'combined', 'stateless' ] }
+
##
# @FirmwareMappingFlash:
#
# Describes loading and mapping properties for the firmware executable
# and its accompanying NVRAM file, when @FirmwareDevice is @flash.
#
-# @executable: Identifies the firmware executable. The firmware
-# executable may be shared by multiple virtual machine
-# definitions. The preferred corresponding QEMU command
-# line options are
+# @mode: Describes how the firmware build handles code versus variable
+# storage. If not present, it must be treated as if it was
+# configured with value ``split``. Since: 7.0.0
+#
+# @executable: Identifies the firmware executable. The @mode
+# indicates whether there will be an associated
+# NVRAM template present. The preferred
+# corresponding QEMU command line options are
# -drive if=none,id=pflash0,readonly=on,file=@executable.@filename,format=@executable.@format
# -machine pflash0=pflash0
-# or equivalent -blockdev instead of -drive.
+# or equivalent -blockdev instead of -drive. When
+# @mode is ``combined`` the executable must be
+# cloned before use and configured with readonly=off.
# With QEMU versions older than 4.0, you have to use
# -drive if=pflash,unit=0,readonly=on,file=@executable.@filename,format=@executable.@format
#
# @nvram-template: Identifies the NVRAM template compatible with
-# @executable. Management software instantiates an
+# @executable, when @mode is set to ``split``,
+# otherwise it should not be present.
+# Management software instantiates an
# individual copy -- a specific NVRAM file -- from
# @nvram-template.@filename for each new virtual
# machine definition created. @nvram-template.@filename
@@ -246,8 +283,9 @@
# Since: 3.0
##
{ 'struct' : 'FirmwareMappingFlash',
- 'data' : { 'executable' : 'FirmwareFlashFile',
- 'nvram-template' : 'FirmwareFlashFile' } }
+ 'data' : { '*mode': 'FirmwareFlashMode',
+ 'executable' : 'FirmwareFlashFile',
+ '*nvram-template' : 'FirmwareFlashFile' } }
##
# @FirmwareMappingKernel:
diff --git a/docs/system/qemu-block-drivers.rst.inc b/docs/system/qemu-block-drivers.rst.inc
index e313784..dfe5d22 100644
--- a/docs/system/qemu-block-drivers.rst.inc
+++ b/docs/system/qemu-block-drivers.rst.inc
@@ -778,10 +778,32 @@ The optional *HOST_KEY_CHECK* parameter controls how the remote
host's key is checked. The default is ``yes`` which means to use
the local ``.ssh/known_hosts`` file. Setting this to ``no``
turns off known-hosts checking. Or you can check that the host key
-matches a specific fingerprint:
-``host_key_check=md5:78:45:8e:14:57:4f:d5:45:83:0a:0e:f3:49:82:c9:c8``
-(``sha1:`` can also be used as a prefix, but note that OpenSSH
-tools only use MD5 to print fingerprints).
+matches a specific fingerprint. The fingerprint can be provided in
+``md5``, ``sha1``, or ``sha256`` format, however, it is strongly
+recommended to only use ``sha256``, since the other options are
+considered insecure by modern standards. The fingerprint value
+must be given as a hex encoded string::
+
+ host_key_check=sha256:04ce2ae89ff4295a6b9c4111640bdcb3297858ee55cb434d9dd88796e93aa795
+
+The key string may optionally contain ":" separators between
+each pair of hex digits.
+
+The ``$HOME/.ssh/known_hosts`` file contains the base64 encoded
+host keys. These can be converted into the format needed for
+QEMU using a command such as::
+
+ $ for key in `grep 10.33.8.112 known_hosts | awk '{print $3}'`
+ do
+ echo $key | base64 -d | sha256sum
+ done
+ 6c3aa525beda9dc83eadfbd7e5ba7d976ecb59575d1633c87cd06ed2ed6e366f -
+ 12214fd9ea5b408086f98ecccd9958609bd9ac7c0ea316734006bc7818b45dc8 -
+ d36420137bcbd101209ef70c3b15dc07362fbe0fa53c5b135eba6e6afa82f0ce -
+
+Note that there can be multiple keys present per host, each with
+different key ciphers. Care is needed to pick the key fingerprint
+that matches the cipher QEMU will negotiate with the remote server.
Currently authentication must be done using ssh-agent. Other
authentication methods may be supported in future.
diff --git a/softmmu/qemu-seccomp.c b/softmmu/qemu-seccomp.c
index f500267..deaf8a4 100644
--- a/softmmu/qemu-seccomp.c
+++ b/softmmu/qemu-seccomp.c
@@ -38,6 +38,7 @@ struct QemuSeccompSyscall {
uint8_t set;
uint8_t narg;
const struct scmp_arg_cmp *arg_cmp;
+ uint32_t action;
};
const struct scmp_arg_cmp sched_setscheduler_arg[] = {
@@ -45,63 +46,223 @@ const struct scmp_arg_cmp sched_setscheduler_arg[] = {
{ .arg = 1, .op = SCMP_CMP_NE, .datum_a = SCHED_IDLE }
};
+/*
+ * See 'NOTES' in 'man 2 clone' - s390 & cross have 'flags' in
+ * different position to other architectures
+ */
+#if defined(HOST_S390X) || defined(HOST_S390) || defined(HOST_CRIS)
+#define CLONE_FLAGS_ARG 1
+#else
+#define CLONE_FLAGS_ARG 0
+#endif
+
+#ifndef CLONE_PIDFD
+# define CLONE_PIDFD 0x00001000
+#endif
+
+#define REQUIRE_CLONE_FLAG(flag) \
+ const struct scmp_arg_cmp clone_arg ## flag[] = { \
+ { .arg = CLONE_FLAGS_ARG, \
+ .op = SCMP_CMP_MASKED_EQ, \
+ .datum_a = flag, .datum_b = 0 } }
+
+#define FORBID_CLONE_FLAG(flag) \
+ const struct scmp_arg_cmp clone_arg ## flag[] = { \
+ { .arg = CLONE_FLAGS_ARG, \
+ .op = SCMP_CMP_MASKED_EQ, \
+ .datum_a = flag, .datum_b = flag } }
+
+#define RULE_CLONE_FLAG(flag) \
+ { SCMP_SYS(clone), QEMU_SECCOMP_SET_SPAWN, \
+ ARRAY_SIZE(clone_arg ## flag), clone_arg ## flag, SCMP_ACT_TRAP }
+
+/* If no CLONE_* flags are set, except CSIGNAL, deny */
+const struct scmp_arg_cmp clone_arg_none[] = {
+ { .arg = CLONE_FLAGS_ARG,
+ .op = SCMP_CMP_MASKED_EQ,
+ .datum_a = ~(CSIGNAL), .datum_b = 0 }
+};
+
+/*
+ * pthread_create should always set all of these.
+ */
+REQUIRE_CLONE_FLAG(CLONE_VM);
+REQUIRE_CLONE_FLAG(CLONE_FS);
+REQUIRE_CLONE_FLAG(CLONE_FILES);
+REQUIRE_CLONE_FLAG(CLONE_SIGHAND);
+REQUIRE_CLONE_FLAG(CLONE_THREAD);
+REQUIRE_CLONE_FLAG(CLONE_SYSVSEM);
+REQUIRE_CLONE_FLAG(CLONE_SETTLS);
+REQUIRE_CLONE_FLAG(CLONE_PARENT_SETTID);
+REQUIRE_CLONE_FLAG(CLONE_CHILD_CLEARTID);
+/*
+ * Musl sets this in pthread_create too, but it is
+ * obsolete and harmless since its behaviour is
+ * subsumed under CLONE_THREAD
+ */
+/*REQUIRE_CLONE_FLAG(CLONE_DETACHED);*/
+
+
+/*
+ * These all indicate an attempt to spawn a process
+ * instead of a thread, or other undesirable scenarios
+ */
+FORBID_CLONE_FLAG(CLONE_PIDFD);
+FORBID_CLONE_FLAG(CLONE_PTRACE);
+FORBID_CLONE_FLAG(CLONE_VFORK);
+FORBID_CLONE_FLAG(CLONE_PARENT);
+FORBID_CLONE_FLAG(CLONE_NEWNS);
+FORBID_CLONE_FLAG(CLONE_UNTRACED);
+FORBID_CLONE_FLAG(CLONE_NEWCGROUP);
+FORBID_CLONE_FLAG(CLONE_NEWUTS);
+FORBID_CLONE_FLAG(CLONE_NEWIPC);
+FORBID_CLONE_FLAG(CLONE_NEWUSER);
+FORBID_CLONE_FLAG(CLONE_NEWPID);
+FORBID_CLONE_FLAG(CLONE_NEWNET);
+FORBID_CLONE_FLAG(CLONE_IO);
+
+
static const struct QemuSeccompSyscall denylist[] = {
/* default set of syscalls that should get blocked */
- { SCMP_SYS(reboot), QEMU_SECCOMP_SET_DEFAULT },
- { SCMP_SYS(swapon), QEMU_SECCOMP_SET_DEFAULT },
- { SCMP_SYS(swapoff), QEMU_SECCOMP_SET_DEFAULT },
- { SCMP_SYS(syslog), QEMU_SECCOMP_SET_DEFAULT },
- { SCMP_SYS(mount), QEMU_SECCOMP_SET_DEFAULT },
- { SCMP_SYS(umount), QEMU_SECCOMP_SET_DEFAULT },
- { SCMP_SYS(kexec_load), QEMU_SECCOMP_SET_DEFAULT },
- { SCMP_SYS(afs_syscall), QEMU_SECCOMP_SET_DEFAULT },
- { SCMP_SYS(break), QEMU_SECCOMP_SET_DEFAULT },
- { SCMP_SYS(ftime), QEMU_SECCOMP_SET_DEFAULT },
- { SCMP_SYS(getpmsg), QEMU_SECCOMP_SET_DEFAULT },
- { SCMP_SYS(gtty), QEMU_SECCOMP_SET_DEFAULT },
- { SCMP_SYS(lock), QEMU_SECCOMP_SET_DEFAULT },
- { SCMP_SYS(mpx), QEMU_SECCOMP_SET_DEFAULT },
- { SCMP_SYS(prof), QEMU_SECCOMP_SET_DEFAULT },
- { SCMP_SYS(profil), QEMU_SECCOMP_SET_DEFAULT },
- { SCMP_SYS(putpmsg), QEMU_SECCOMP_SET_DEFAULT },
- { SCMP_SYS(security), QEMU_SECCOMP_SET_DEFAULT },
- { SCMP_SYS(stty), QEMU_SECCOMP_SET_DEFAULT },
- { SCMP_SYS(tuxcall), QEMU_SECCOMP_SET_DEFAULT },
- { SCMP_SYS(ulimit), QEMU_SECCOMP_SET_DEFAULT },
- { SCMP_SYS(vserver), QEMU_SECCOMP_SET_DEFAULT },
+ { SCMP_SYS(reboot), QEMU_SECCOMP_SET_DEFAULT,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(swapon), QEMU_SECCOMP_SET_DEFAULT,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(swapoff), QEMU_SECCOMP_SET_DEFAULT,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(syslog), QEMU_SECCOMP_SET_DEFAULT,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(mount), QEMU_SECCOMP_SET_DEFAULT,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(umount), QEMU_SECCOMP_SET_DEFAULT,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(kexec_load), QEMU_SECCOMP_SET_DEFAULT,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(afs_syscall), QEMU_SECCOMP_SET_DEFAULT,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(break), QEMU_SECCOMP_SET_DEFAULT,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(ftime), QEMU_SECCOMP_SET_DEFAULT,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(getpmsg), QEMU_SECCOMP_SET_DEFAULT,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(gtty), QEMU_SECCOMP_SET_DEFAULT,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(lock), QEMU_SECCOMP_SET_DEFAULT,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(mpx), QEMU_SECCOMP_SET_DEFAULT,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(prof), QEMU_SECCOMP_SET_DEFAULT,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(profil), QEMU_SECCOMP_SET_DEFAULT,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(putpmsg), QEMU_SECCOMP_SET_DEFAULT,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(security), QEMU_SECCOMP_SET_DEFAULT,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(stty), QEMU_SECCOMP_SET_DEFAULT,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(tuxcall), QEMU_SECCOMP_SET_DEFAULT,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(ulimit), QEMU_SECCOMP_SET_DEFAULT,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(vserver), QEMU_SECCOMP_SET_DEFAULT,
+ 0, NULL, SCMP_ACT_TRAP },
/* obsolete */
- { SCMP_SYS(readdir), QEMU_SECCOMP_SET_OBSOLETE },
- { SCMP_SYS(_sysctl), QEMU_SECCOMP_SET_OBSOLETE },
- { SCMP_SYS(bdflush), QEMU_SECCOMP_SET_OBSOLETE },
- { SCMP_SYS(create_module), QEMU_SECCOMP_SET_OBSOLETE },
- { SCMP_SYS(get_kernel_syms), QEMU_SECCOMP_SET_OBSOLETE },
- { SCMP_SYS(query_module), QEMU_SECCOMP_SET_OBSOLETE },
- { SCMP_SYS(sgetmask), QEMU_SECCOMP_SET_OBSOLETE },
- { SCMP_SYS(ssetmask), QEMU_SECCOMP_SET_OBSOLETE },
- { SCMP_SYS(sysfs), QEMU_SECCOMP_SET_OBSOLETE },
- { SCMP_SYS(uselib), QEMU_SECCOMP_SET_OBSOLETE },
- { SCMP_SYS(ustat), QEMU_SECCOMP_SET_OBSOLETE },
+ { SCMP_SYS(readdir), QEMU_SECCOMP_SET_OBSOLETE,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(_sysctl), QEMU_SECCOMP_SET_OBSOLETE,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(bdflush), QEMU_SECCOMP_SET_OBSOLETE,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(create_module), QEMU_SECCOMP_SET_OBSOLETE,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(get_kernel_syms), QEMU_SECCOMP_SET_OBSOLETE,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(query_module), QEMU_SECCOMP_SET_OBSOLETE,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(sgetmask), QEMU_SECCOMP_SET_OBSOLETE,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(ssetmask), QEMU_SECCOMP_SET_OBSOLETE,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(sysfs), QEMU_SECCOMP_SET_OBSOLETE,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(uselib), QEMU_SECCOMP_SET_OBSOLETE,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(ustat), QEMU_SECCOMP_SET_OBSOLETE,
+ 0, NULL, SCMP_ACT_TRAP },
/* privileged */
- { SCMP_SYS(setuid), QEMU_SECCOMP_SET_PRIVILEGED },
- { SCMP_SYS(setgid), QEMU_SECCOMP_SET_PRIVILEGED },
- { SCMP_SYS(setpgid), QEMU_SECCOMP_SET_PRIVILEGED },
- { SCMP_SYS(setsid), QEMU_SECCOMP_SET_PRIVILEGED },
- { SCMP_SYS(setreuid), QEMU_SECCOMP_SET_PRIVILEGED },
- { SCMP_SYS(setregid), QEMU_SECCOMP_SET_PRIVILEGED },
- { SCMP_SYS(setresuid), QEMU_SECCOMP_SET_PRIVILEGED },
- { SCMP_SYS(setresgid), QEMU_SECCOMP_SET_PRIVILEGED },
- { SCMP_SYS(setfsuid), QEMU_SECCOMP_SET_PRIVILEGED },
- { SCMP_SYS(setfsgid), QEMU_SECCOMP_SET_PRIVILEGED },
+ { SCMP_SYS(setuid), QEMU_SECCOMP_SET_PRIVILEGED,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(setgid), QEMU_SECCOMP_SET_PRIVILEGED,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(setpgid), QEMU_SECCOMP_SET_PRIVILEGED,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(setsid), QEMU_SECCOMP_SET_PRIVILEGED,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(setreuid), QEMU_SECCOMP_SET_PRIVILEGED,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(setregid), QEMU_SECCOMP_SET_PRIVILEGED,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(setresuid), QEMU_SECCOMP_SET_PRIVILEGED,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(setresgid), QEMU_SECCOMP_SET_PRIVILEGED,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(setfsuid), QEMU_SECCOMP_SET_PRIVILEGED,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(setfsgid), QEMU_SECCOMP_SET_PRIVILEGED,
+ 0, NULL, SCMP_ACT_TRAP },
/* spawn */
- { SCMP_SYS(fork), QEMU_SECCOMP_SET_SPAWN },
- { SCMP_SYS(vfork), QEMU_SECCOMP_SET_SPAWN },
- { SCMP_SYS(execve), QEMU_SECCOMP_SET_SPAWN },
+ { SCMP_SYS(fork), QEMU_SECCOMP_SET_SPAWN,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(vfork), QEMU_SECCOMP_SET_SPAWN,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(execve), QEMU_SECCOMP_SET_SPAWN,
+ 0, NULL, SCMP_ACT_TRAP },
+ { SCMP_SYS(clone), QEMU_SECCOMP_SET_SPAWN,
+ ARRAY_SIZE(clone_arg_none), clone_arg_none, SCMP_ACT_TRAP },
+ RULE_CLONE_FLAG(CLONE_VM),
+ RULE_CLONE_FLAG(CLONE_FS),
+ RULE_CLONE_FLAG(CLONE_FILES),
+ RULE_CLONE_FLAG(CLONE_SIGHAND),
+ RULE_CLONE_FLAG(CLONE_THREAD),
+ RULE_CLONE_FLAG(CLONE_SYSVSEM),
+ RULE_CLONE_FLAG(CLONE_SETTLS),
+ RULE_CLONE_FLAG(CLONE_PARENT_SETTID),
+ RULE_CLONE_FLAG(CLONE_CHILD_CLEARTID),
+ /*RULE_CLONE_FLAG(CLONE_DETACHED),*/
+ RULE_CLONE_FLAG(CLONE_PIDFD),
+ RULE_CLONE_FLAG(CLONE_PTRACE),
+ RULE_CLONE_FLAG(CLONE_VFORK),
+ RULE_CLONE_FLAG(CLONE_PARENT),
+ RULE_CLONE_FLAG(CLONE_NEWNS),
+ RULE_CLONE_FLAG(CLONE_UNTRACED),
+ RULE_CLONE_FLAG(CLONE_NEWCGROUP),
+ RULE_CLONE_FLAG(CLONE_NEWUTS),
+ RULE_CLONE_FLAG(CLONE_NEWIPC),
+ RULE_CLONE_FLAG(CLONE_NEWUSER),
+ RULE_CLONE_FLAG(CLONE_NEWPID),
+ RULE_CLONE_FLAG(CLONE_NEWNET),
+ RULE_CLONE_FLAG(CLONE_IO),
+#ifdef __SNR_clone3
+ { SCMP_SYS(clone3), QEMU_SECCOMP_SET_SPAWN,
+ 0, NULL, SCMP_ACT_ERRNO(ENOSYS) },
+#endif
+#ifdef __SNR_execveat
+ { SCMP_SYS(execveat), QEMU_SECCOMP_SET_SPAWN },
+#endif
+ { SCMP_SYS(setns), QEMU_SECCOMP_SET_SPAWN },
+ { SCMP_SYS(unshare), QEMU_SECCOMP_SET_SPAWN },
/* resource control */
- { SCMP_SYS(setpriority), QEMU_SECCOMP_SET_RESOURCECTL },
- { SCMP_SYS(sched_setparam), QEMU_SECCOMP_SET_RESOURCECTL },
+ { SCMP_SYS(setpriority), QEMU_SECCOMP_SET_RESOURCECTL,
+ 0, NULL, SCMP_ACT_ERRNO(EPERM) },
+ { SCMP_SYS(sched_setparam), QEMU_SECCOMP_SET_RESOURCECTL,
+ 0, NULL, SCMP_ACT_ERRNO(EPERM) },
{ SCMP_SYS(sched_setscheduler), QEMU_SECCOMP_SET_RESOURCECTL,
- ARRAY_SIZE(sched_setscheduler_arg), sched_setscheduler_arg },
- { SCMP_SYS(sched_setaffinity), QEMU_SECCOMP_SET_RESOURCECTL },
+ ARRAY_SIZE(sched_setscheduler_arg), sched_setscheduler_arg,
+ SCMP_ACT_ERRNO(EPERM) },
+ { SCMP_SYS(sched_setaffinity), QEMU_SECCOMP_SET_RESOURCECTL,
+ 0, NULL, SCMP_ACT_ERRNO(EPERM) },
};
static inline __attribute__((unused)) int
@@ -115,15 +276,11 @@ qemu_seccomp(unsigned int operation, unsigned int flags, void *args)
#endif
}
-static uint32_t qemu_seccomp_get_action(int set)
+static uint32_t qemu_seccomp_update_action(uint32_t action)
{
- switch (set) {
- case QEMU_SECCOMP_SET_DEFAULT:
- case QEMU_SECCOMP_SET_OBSOLETE:
- case QEMU_SECCOMP_SET_PRIVILEGED:
- case QEMU_SECCOMP_SET_SPAWN: {
#if defined(SECCOMP_GET_ACTION_AVAIL) && defined(SCMP_ACT_KILL_PROCESS) && \
defined(SECCOMP_RET_KILL_PROCESS)
+ if (action == SCMP_ACT_TRAP) {
static int kill_process = -1;
if (kill_process == -1) {
uint32_t action = SECCOMP_RET_KILL_PROCESS;
@@ -137,16 +294,9 @@ static uint32_t qemu_seccomp_get_action(int set)
if (kill_process == 1) {
return SCMP_ACT_KILL_PROCESS;
}
-#endif
- return SCMP_ACT_TRAP;
- }
-
- case QEMU_SECCOMP_SET_RESOURCECTL:
- return SCMP_ACT_ERRNO(EPERM);
-
- default:
- g_assert_not_reached();
}
+#endif
+ return action;
}
@@ -175,7 +325,7 @@ static int seccomp_start(uint32_t seccomp_opts, Error **errp)
continue;
}
- action = qemu_seccomp_get_action(denylist[i].set);
+ action = qemu_seccomp_update_action(denylist[i].action);
rc = seccomp_rule_add_array(ctx, action, denylist[i].num,
denylist[i].narg, denylist[i].arg_cmp);
if (rc < 0) {
diff --git a/tests/unit/meson.build b/tests/unit/meson.build
index 3a51759..96b2952 100644
--- a/tests/unit/meson.build
+++ b/tests/unit/meson.build
@@ -53,6 +53,10 @@ if have_system or have_tools
tests += {
'test-qmp-event': [testqapi],
}
+
+ if seccomp.found()
+ tests += {'test-seccomp': ['../../softmmu/qemu-seccomp.c', seccomp]}
+ endif
endif
if have_block
diff --git a/tests/unit/test-seccomp.c b/tests/unit/test-seccomp.c
new file mode 100644
index 0000000..3d7771e
--- /dev/null
+++ b/tests/unit/test-seccomp.c
@@ -0,0 +1,269 @@
+/*
+ * QEMU seccomp test suite
+ *
+ * Copyright (c) 2021 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/config-file.h"
+#include "qemu/option.h"
+#include "sysemu/seccomp.h"
+#include "qapi/error.h"
+#include "qemu/module.h"
+
+#include <unistd.h>
+#include <sys/syscall.h>
+
+static void test_seccomp_helper(const char *args, bool killed,
+ int errnum, int (*doit)(void))
+{
+ if (g_test_subprocess()) {
+ QemuOptsList *olist;
+ QemuOpts *opts;
+ int ret;
+
+ module_call_init(MODULE_INIT_OPTS);
+ olist = qemu_find_opts("sandbox");
+ g_assert(olist != NULL);
+
+ opts = qemu_opts_parse_noisily(olist, args, true);
+ g_assert(opts != NULL);
+
+ parse_sandbox(NULL, opts, &error_abort);
+
+ /* Running in a child process */
+ ret = doit();
+
+ if (errnum != 0) {
+ g_assert(ret != 0);
+ g_assert(errno == errnum);
+ } else {
+ g_assert(ret == 0);
+ }
+
+ _exit(0);
+ } else {
+ /* Running in main test process, spawning the child */
+ g_test_trap_subprocess(NULL, 0, 0);
+ if (killed) {
+ g_test_trap_assert_failed();
+ } else {
+ g_test_trap_assert_passed();
+ }
+ }
+}
+
+
+static void test_seccomp_killed(const char *args, int (*doit)(void))
+{
+ test_seccomp_helper(args, true, 0, doit);
+}
+
+static void test_seccomp_errno(const char *args, int errnum, int (*doit)(void))
+{
+ test_seccomp_helper(args, false, errnum, doit);
+}
+
+static void test_seccomp_passed(const char *args, int (*doit)(void))
+{
+ test_seccomp_helper(args, false, 0, doit);
+}
+
+#ifdef SYS_fork
+static int doit_sys_fork(void)
+{
+ int ret = syscall(SYS_fork);
+ if (ret < 0) {
+ return ret;
+ }
+ if (ret == 0) {
+ _exit(0);
+ }
+ return 0;
+}
+
+static void test_seccomp_sys_fork_on_nospawn(void)
+{
+ test_seccomp_killed("on,spawn=deny", doit_sys_fork);
+}
+
+static void test_seccomp_sys_fork_on(void)
+{
+ test_seccomp_passed("on", doit_sys_fork);
+}
+
+static void test_seccomp_sys_fork_off(void)
+{
+ test_seccomp_passed("off", doit_sys_fork);
+}
+#endif
+
+static int doit_fork(void)
+{
+ int ret = fork();
+ if (ret < 0) {
+ return ret;
+ }
+ if (ret == 0) {
+ _exit(0);
+ }
+ return 0;
+}
+
+static void test_seccomp_fork_on_nospawn(void)
+{
+ test_seccomp_killed("on,spawn=deny", doit_fork);
+}
+
+static void test_seccomp_fork_on(void)
+{
+ test_seccomp_passed("on", doit_fork);
+}
+
+static void test_seccomp_fork_off(void)
+{
+ test_seccomp_passed("off", doit_fork);
+}
+
+static void *noop(void *arg)
+{
+ return arg;
+}
+
+static int doit_thread(void)
+{
+ pthread_t th;
+ int ret = pthread_create(&th, NULL, noop, NULL);
+ if (ret != 0) {
+ errno = ret;
+ return -1;
+ } else {
+ pthread_join(th, NULL);
+ return 0;
+ }
+}
+
+static void test_seccomp_thread_on(void)
+{
+ test_seccomp_passed("on", doit_thread);
+}
+
+static void test_seccomp_thread_on_nospawn(void)
+{
+ test_seccomp_passed("on,spawn=deny", doit_thread);
+}
+
+static void test_seccomp_thread_off(void)
+{
+ test_seccomp_passed("off", doit_thread);
+}
+
+static int doit_sched(void)
+{
+ struct sched_param param = { .sched_priority = 0 };
+ return sched_setscheduler(getpid(), SCHED_OTHER, &param);
+}
+
+static void test_seccomp_sched_on_nores(void)
+{
+ test_seccomp_errno("on,resourcecontrol=deny", EPERM, doit_sched);
+}
+
+static void test_seccomp_sched_on(void)
+{
+ test_seccomp_passed("on", doit_sched);
+}
+
+static void test_seccomp_sched_off(void)
+{
+ test_seccomp_passed("off", doit_sched);
+}
+
+static bool can_play_with_seccomp(void)
+{
+ g_autofree char *status = NULL;
+ g_auto(GStrv) lines = NULL;
+ size_t i;
+
+ if (!g_file_get_contents("/proc/self/status", &status, NULL, NULL)) {
+ return false;
+ }
+
+ lines = g_strsplit(status, "\n", 0);
+
+ for (i = 0; lines[i] != NULL; i++) {
+ if (g_str_has_prefix(lines[i], "Seccomp:")) {
+ /*
+ * "Seccomp: 1" or "Seccomp: 2" indicate we're already
+ * confined, probably as we're inside a container. In
+ * this case our tests might get unexpected results,
+ * so we can't run reliably
+ */
+ if (!strchr(lines[i], '0')) {
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ /* Doesn't look like seccomp is enabled in the kernel */
+ return false;
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+ if (can_play_with_seccomp()) {
+#ifdef SYS_fork
+ g_test_add_func("/softmmu/seccomp/sys-fork/on",
+ test_seccomp_sys_fork_on);
+ g_test_add_func("/softmmu/seccomp/sys-fork/on-nospawn",
+ test_seccomp_sys_fork_on_nospawn);
+ g_test_add_func("/softmmu/seccomp/sys-fork/off",
+ test_seccomp_sys_fork_off);
+#endif
+
+ g_test_add_func("/softmmu/seccomp/fork/on",
+ test_seccomp_fork_on);
+ g_test_add_func("/softmmu/seccomp/fork/on-nospawn",
+ test_seccomp_fork_on_nospawn);
+ g_test_add_func("/softmmu/seccomp/fork/off",
+ test_seccomp_fork_off);
+
+ g_test_add_func("/softmmu/seccomp/thread/on",
+ test_seccomp_thread_on);
+ g_test_add_func("/softmmu/seccomp/thread/on-nospawn",
+ test_seccomp_thread_on_nospawn);
+ g_test_add_func("/softmmu/seccomp/thread/off",
+ test_seccomp_thread_off);
+
+ if (doit_sched() == 0) {
+ /*
+ * musl doesn't impl sched_setscheduler, hence
+ * we check above if it works first
+ */
+ g_test_add_func("/softmmu/seccomp/sched/on",
+ test_seccomp_sched_on);
+ g_test_add_func("/softmmu/seccomp/sched/on-nores",
+ test_seccomp_sched_on_nores);
+ g_test_add_func("/softmmu/seccomp/sched/off",
+ test_seccomp_sched_off);
+ }
+ }
+ return g_test_run();
+}