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
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEE2vOm/bJrYpEtDo4/vobrtBUQT98FAmIOOBkACgkQvobrtBUQ
 T9/ruhAAr8jkAH8FN5ftx2/L7q8SHpjPupue1CJ0Nl/ykmYhTGc+SqC3R2nZWOk2
 Ws8hHVcDVT1lhrGxPtU7o+JPC1TebJTsloimJoKQY3qfdvZadJeR/4KsOUzi2ruu
 VZ6HiYvZc1c9T+NPf3QRhBo7yyascKWKWHDseUNIt/2DiefCox4QFUDDMG86HiQF
 KK30xWTvwJdcPxRlbfZbWRoqA0v4OoSDK6Ftp94FQSNBkExO85kstDq3xVaApf8H
 DE1QD7gf+dvz11wVuFhrf4d1EH032nU0p0kMxhABc4/kZXo5iWXohhzML3/MUEVT
 pe5/9pzUdWpfXQd/2r7x2PyPgySAG7lGbkgltowY52qnRPaNw9ukwkFCFAj8wiD8
 FT2ghvkYD3zLfnZ3nuuzJVjf3pXgCc5VcfXaoffT72a7gpI1LTuEqPFwo04imV4l
 21fYFx26mYTGCLH1CwVw8MQ2z/dg6uorT/NHdmRA/KrYJ1Elay2K7DV3Z5jOM5MI
 0Ll5HkfsUut+1rioUjNgmlQ+96k/G0P0hVUoTUIcgl3U/GDx2+ypcrNTfmEcaCLV
 bOhsjtrcg/KAXsCSbvnfDe3bWf0txnscyqoilEzDahLvciWG3d6qlhczLy29LGb4
 /w7iqnUcSygXc+a9/ckVo1h5fo0i9qb3W8Pw9klapvz6SGJ83g4=
 =PeCY
 -----END PGP SIGNATURE-----

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>
This commit is contained in:
Peter Maydell 2022-02-23 09:25:05 +00:00
commit 4aa2e497a9
7 changed files with 599 additions and 87 deletions

View file

@ -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>

View file

@ -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 {

View file

@ -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:

View file

@ -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.

View file

@ -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();
}
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) {

View file

@ -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

269
tests/unit/test-seccomp.c Normal file
View file

@ -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();
}