2013-04-29 15:37:07 +00:00
|
|
|
#!/usr/bin/perl
|
|
|
|
#
|
event: move event filtering to daemon (regression fix)
https://bugzilla.redhat.com/show_bug.cgi?id=1058839
Commit f9f56340 for CVE-2014-0028 almost had the right idea - we
need to check the ACL rules to filter which events to send. But
it overlooked one thing: the event dispatch queue is running in
the main loop thread, and therefore does not normally have a
current virIdentityPtr. But filter checks can be based on current
identity, so when libvirtd.conf contains access_drivers=["polkit"],
we ended up rejecting access for EVERY event due to failure to
look up the current identity, even if it should have been allowed.
Furthermore, even for events that are triggered by API calls, it
is important to remember that the point of events is that they can
be copied across multiple connections, which may have separate
identities and permissions. So even if events were dispatched
from a context where we have an identity, we must change to the
correct identity of the connection that will be receiving the
event, rather than basing a decision on the context that triggered
the event, when deciding whether to filter an event to a
particular connection.
If there were an easy way to get from virConnectPtr to the
appropriate virIdentityPtr, then object_event.c could adjust the
identity prior to checking whether to dispatch an event. But
setting up that back-reference is a bit invasive. Instead, it
is easier to delay the filtering check until lower down the
stack, at the point where we have direct access to the RPC
client object that owns an identity. As such, this patch ends
up reverting a large portion of the framework of commit f9f56340.
We also have to teach 'make check' to special-case the fact that
the event registration filtering is done at the point of dispatch,
rather than the point of registration. Note that even though we
don't actually use virConnectDomainEventRegisterCheckACL (because
the RegisterAny variant is sufficient), we still generate the
function for the purposes of documenting that the filtering
takes place.
Also note that I did not entirely delete the notion of a filter
from object_event.c; I still plan on using that for my upcoming
patch series for qemu monitor events in libvirt-qemu.so. In
other words, while this patch changes ACL filtering to live in
remote.c and therefore we have no current client of the filtering
in object_event.c, the notion of filtering in object_event.c is
still useful down the road.
* src/check-aclrules.pl: Exempt event registration from having to
pass checkACL filter down call stack.
* daemon/remote.c (remoteRelayDomainEventCheckACL)
(remoteRelayNetworkEventCheckACL): New functions.
(remoteRelay*Event*): Use new functions.
* src/conf/domain_event.h (virDomainEventStateRegister)
(virDomainEventStateRegisterID): Drop unused parameter.
* src/conf/network_event.h (virNetworkEventStateRegisterID):
Likewise.
* src/conf/domain_event.c (virDomainEventFilter): Delete unused
function.
* src/conf/network_event.c (virNetworkEventFilter): Likewise.
* src/libxl/libxl_driver.c: Adjust caller.
* src/lxc/lxc_driver.c: Likewise.
* src/network/bridge_driver.c: Likewise.
* src/qemu/qemu_driver.c: Likewise.
* src/remote/remote_driver.c: Likewise.
* src/test/test_driver.c: Likewise.
* src/uml/uml_driver.c: Likewise.
* src/vbox/vbox_tmpl.c: Likewise.
* src/xen/xen_driver.c: Likewise.
Signed-off-by: Eric Blake <eblake@redhat.com>
2014-01-28 21:50:02 +00:00
|
|
|
# Copyright (C) 2013-2014 Red Hat, Inc.
|
2013-04-29 15:37:07 +00:00
|
|
|
#
|
|
|
|
# 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/>.
|
|
|
|
#
|
|
|
|
# This script validates that the driver implementation of any
|
|
|
|
# public APIs contain ACL checks.
|
|
|
|
#
|
|
|
|
# As the script reads each source file, it attempts to identify
|
|
|
|
# top level function names.
|
|
|
|
#
|
|
|
|
# When reading the body of the functions, it looks for anything
|
|
|
|
# that looks like an API called named XXXEnsureACL. It will
|
|
|
|
# validate that the XXX prefix matches the name of the function
|
|
|
|
# it occurs in.
|
|
|
|
#
|
|
|
|
# When it later finds the virDriverPtr table, for each entry
|
|
|
|
# point listed, it will validate if there was a previously
|
|
|
|
# detected EnsureACL call recorded.
|
|
|
|
#
|
|
|
|
use strict;
|
|
|
|
use warnings;
|
|
|
|
|
|
|
|
my $status = 0;
|
|
|
|
|
|
|
|
my $brace = 0;
|
|
|
|
my $maybefunc;
|
|
|
|
my $intable = 0;
|
|
|
|
my $table;
|
|
|
|
|
|
|
|
my %acls;
|
2013-06-27 16:12:20 +00:00
|
|
|
my %aclfilters;
|
2013-04-29 15:37:07 +00:00
|
|
|
|
|
|
|
my %whitelist = (
|
|
|
|
"connectClose" => 1,
|
|
|
|
"connectIsEncrypted" => 1,
|
|
|
|
"connectIsSecure" => 1,
|
|
|
|
"connectIsAlive" => 1,
|
|
|
|
"networkOpen" => 1,
|
|
|
|
"networkClose" => 1,
|
|
|
|
"nwfilterOpen" => 1,
|
|
|
|
"nwfilterClose" => 1,
|
|
|
|
"secretOpen" => 1,
|
|
|
|
"secretClose" => 1,
|
|
|
|
"storageOpen" => 1,
|
|
|
|
"storageClose" => 1,
|
|
|
|
"interfaceOpen" => 1,
|
|
|
|
"interfaceClose" => 1,
|
|
|
|
);
|
|
|
|
|
|
|
|
# Temp hack - remove it once xen driver is fixed
|
|
|
|
my %implwhitelist = (
|
|
|
|
"xenUnifiedDomainRestore" => 1,
|
|
|
|
"xenUnifiedDomainRestoreFlags" => 1,
|
|
|
|
"xenUnifiedDomainMigratePrepare" => 1,
|
|
|
|
"xenUnifiedNodeDeviceDettach" => 1,
|
|
|
|
"xenUnifiedNodeDeviceDetachFlags" => 1,
|
|
|
|
"xenUnifiedNodeDeviceReset" => 1,
|
|
|
|
"xenUnifiedDomainIsActive" => 1,
|
|
|
|
"xenUnifiedDomainIsPersistent" => 1,
|
|
|
|
"xenUnifiedDomainIsUpdated" => 1,
|
|
|
|
"xenUnifiedDomainOpenConsole" => 1,
|
2016-06-24 14:32:38 +00:00
|
|
|
"vzDomainMigrateConfirm3Params" => 1,
|
2013-04-29 15:37:07 +00:00
|
|
|
);
|
2013-06-27 16:12:20 +00:00
|
|
|
my %filterimplwhitelist = (
|
|
|
|
"xenUnifiedConnectListDomains" => 1,
|
|
|
|
"xenUnifiedConnectNumOfDomains" => 1,
|
|
|
|
"xenUnifiedConnectListDefinedDomains" => 1,
|
|
|
|
"xenUnifiedConnectNumOfDefinedDomains" => 1,
|
|
|
|
);
|
2013-04-29 15:37:07 +00:00
|
|
|
|
|
|
|
my $lastfile;
|
|
|
|
|
2013-06-27 16:12:20 +00:00
|
|
|
sub fixup_name {
|
|
|
|
my $name = shift;
|
|
|
|
|
|
|
|
$name =~ s/Nwfilter/NWFilter/;
|
|
|
|
$name =~ s/Xml$/XML/;
|
|
|
|
$name =~ s/Uri$/URI/;
|
|
|
|
$name =~ s/Uuid$/UUID/;
|
|
|
|
$name =~ s/Id$/ID/;
|
|
|
|
$name =~ s/Mac$/MAC/;
|
|
|
|
$name =~ s/Cpu$/CPU/;
|
|
|
|
$name =~ s/Os$/OS/;
|
|
|
|
$name =~ s/Nmi$/NMI/;
|
|
|
|
$name =~ s/Pm/PM/;
|
|
|
|
$name =~ s/Fstrim$/FSTrim/;
|
|
|
|
$name =~ s/Scsi/SCSI/;
|
|
|
|
$name =~ s/Wwn$/WWN/;
|
|
|
|
|
|
|
|
return $name;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub name_to_ProcName {
|
|
|
|
my $name = shift;
|
|
|
|
|
|
|
|
my @elems;
|
|
|
|
if ($name =~ /_/ || (lc $name) eq "open" || (lc $name) eq "close") {
|
|
|
|
@elems = split /_/, $name;
|
|
|
|
@elems = map lc, @elems;
|
|
|
|
@elems = map ucfirst, @elems;
|
|
|
|
} else {
|
|
|
|
@elems = $name;
|
|
|
|
}
|
|
|
|
@elems = map { fixup_name($_) } @elems;
|
|
|
|
my $procname = join "", @elems;
|
|
|
|
|
|
|
|
$procname =~ s/^([A-Z])/lc $1/e;
|
|
|
|
|
|
|
|
return $procname;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
my $proto = shift @ARGV;
|
|
|
|
|
|
|
|
open PROTO, "<$proto" or die "cannot read $proto";
|
|
|
|
|
|
|
|
my %filtered;
|
|
|
|
my $incomment = 0;
|
|
|
|
my $filtered = 0;
|
|
|
|
while (<PROTO>) {
|
|
|
|
if (m,/\*\*,) {
|
|
|
|
$incomment = 1;
|
|
|
|
$filtered = 0;
|
|
|
|
} elsif ($incomment) {
|
|
|
|
if (m,\*\s\@aclfilter,) {
|
|
|
|
$filtered = 1;
|
|
|
|
} elsif ($filtered &&
|
|
|
|
m,REMOTE_PROC_(.*)\s+=\s*\d+,) {
|
|
|
|
my $api = name_to_ProcName($1);
|
event: move event filtering to daemon (regression fix)
https://bugzilla.redhat.com/show_bug.cgi?id=1058839
Commit f9f56340 for CVE-2014-0028 almost had the right idea - we
need to check the ACL rules to filter which events to send. But
it overlooked one thing: the event dispatch queue is running in
the main loop thread, and therefore does not normally have a
current virIdentityPtr. But filter checks can be based on current
identity, so when libvirtd.conf contains access_drivers=["polkit"],
we ended up rejecting access for EVERY event due to failure to
look up the current identity, even if it should have been allowed.
Furthermore, even for events that are triggered by API calls, it
is important to remember that the point of events is that they can
be copied across multiple connections, which may have separate
identities and permissions. So even if events were dispatched
from a context where we have an identity, we must change to the
correct identity of the connection that will be receiving the
event, rather than basing a decision on the context that triggered
the event, when deciding whether to filter an event to a
particular connection.
If there were an easy way to get from virConnectPtr to the
appropriate virIdentityPtr, then object_event.c could adjust the
identity prior to checking whether to dispatch an event. But
setting up that back-reference is a bit invasive. Instead, it
is easier to delay the filtering check until lower down the
stack, at the point where we have direct access to the RPC
client object that owns an identity. As such, this patch ends
up reverting a large portion of the framework of commit f9f56340.
We also have to teach 'make check' to special-case the fact that
the event registration filtering is done at the point of dispatch,
rather than the point of registration. Note that even though we
don't actually use virConnectDomainEventRegisterCheckACL (because
the RegisterAny variant is sufficient), we still generate the
function for the purposes of documenting that the filtering
takes place.
Also note that I did not entirely delete the notion of a filter
from object_event.c; I still plan on using that for my upcoming
patch series for qemu monitor events in libvirt-qemu.so. In
other words, while this patch changes ACL filtering to live in
remote.c and therefore we have no current client of the filtering
in object_event.c, the notion of filtering in object_event.c is
still useful down the road.
* src/check-aclrules.pl: Exempt event registration from having to
pass checkACL filter down call stack.
* daemon/remote.c (remoteRelayDomainEventCheckACL)
(remoteRelayNetworkEventCheckACL): New functions.
(remoteRelay*Event*): Use new functions.
* src/conf/domain_event.h (virDomainEventStateRegister)
(virDomainEventStateRegisterID): Drop unused parameter.
* src/conf/network_event.h (virNetworkEventStateRegisterID):
Likewise.
* src/conf/domain_event.c (virDomainEventFilter): Delete unused
function.
* src/conf/network_event.c (virNetworkEventFilter): Likewise.
* src/libxl/libxl_driver.c: Adjust caller.
* src/lxc/lxc_driver.c: Likewise.
* src/network/bridge_driver.c: Likewise.
* src/qemu/qemu_driver.c: Likewise.
* src/remote/remote_driver.c: Likewise.
* src/test/test_driver.c: Likewise.
* src/uml/uml_driver.c: Likewise.
* src/vbox/vbox_tmpl.c: Likewise.
* src/xen/xen_driver.c: Likewise.
Signed-off-by: Eric Blake <eblake@redhat.com>
2014-01-28 21:50:02 +00:00
|
|
|
# Event filtering is handled in daemon/remote.c instead of drivers
|
|
|
|
if (! m,_EVENT_REGISTER,) {
|
|
|
|
$filtered{$api} = 1;
|
|
|
|
}
|
2013-06-27 16:12:20 +00:00
|
|
|
$incomment = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
close PROTO;
|
|
|
|
|
2013-04-29 15:37:07 +00:00
|
|
|
while (<>) {
|
|
|
|
if (!defined $lastfile ||
|
|
|
|
$lastfile ne $ARGV) {
|
|
|
|
%acls = ();
|
|
|
|
$brace = 0;
|
|
|
|
$maybefunc = undef;
|
|
|
|
$lastfile = $ARGV;
|
|
|
|
}
|
|
|
|
if ($brace == 0) {
|
|
|
|
# Looks for anything which appears to be a function
|
|
|
|
# body name. Doesn't matter if we pick up bogus stuff
|
|
|
|
# here, as long as we don't miss valid stuff
|
|
|
|
if (m,\b(\w+)\(,) {
|
|
|
|
$maybefunc = $1;
|
|
|
|
}
|
|
|
|
} elsif ($brace > 0) {
|
|
|
|
if (m,(\w+)EnsureACL,) {
|
|
|
|
# Record the fact that maybefunc contains an
|
|
|
|
# ACL call, and make sure it is the right call!
|
|
|
|
my $func = $1;
|
|
|
|
$func =~ s/^vir//;
|
|
|
|
if (!defined $maybefunc) {
|
|
|
|
print "$ARGV:$. Unexpected check '$func' outside function\n";
|
|
|
|
$status = 1;
|
|
|
|
} else {
|
|
|
|
unless ($maybefunc =~ /$func$/i) {
|
|
|
|
print "$ARGV:$. Mismatch check 'vir${func}EnsureACL' for function '$maybefunc'\n";
|
|
|
|
$status = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$acls{$maybefunc} = 1;
|
2013-06-27 16:12:20 +00:00
|
|
|
} elsif (m,(\w+)CheckACL,) {
|
|
|
|
# Record the fact that maybefunc contains an
|
|
|
|
# ACL filter call, and make sure it is the right call!
|
|
|
|
my $func = $1;
|
|
|
|
$func =~ s/^vir//;
|
|
|
|
if (!defined $maybefunc) {
|
|
|
|
print "$ARGV:$. Unexpected check '$func' outside function\n";
|
|
|
|
$status = 1;
|
|
|
|
} else {
|
|
|
|
unless ($maybefunc =~ /$func$/i) {
|
|
|
|
print "$ARGV:$. Mismatch check 'vir${func}CheckACL' for function '$maybefunc'\n";
|
|
|
|
$status = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$aclfilters{$maybefunc} = 1;
|
2013-04-29 15:37:07 +00:00
|
|
|
} elsif (m,\b(\w+)\(,) {
|
|
|
|
# Handles case where we replaced an API with a new
|
|
|
|
# one which adds new parameters, and we're left with
|
|
|
|
# a simple stub calling the new API.
|
|
|
|
my $callfunc = $1;
|
|
|
|
if (exists $acls{$callfunc}) {
|
|
|
|
$acls{$maybefunc} = 1;
|
|
|
|
}
|
2013-06-27 16:12:20 +00:00
|
|
|
if (exists $aclfilters{$callfunc}) {
|
|
|
|
$aclfilters{$maybefunc} = 1;
|
|
|
|
}
|
2013-04-29 15:37:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# Pass the vir*DriverPtr tables and make sure that
|
|
|
|
# every func listed there, has an impl which calls
|
|
|
|
# an ACL function
|
|
|
|
if ($intable) {
|
|
|
|
if (/\}/) {
|
|
|
|
$intable = 0;
|
|
|
|
$table = undef;
|
|
|
|
} elsif (/\.(\w+)\s*=\s*(\w+),?/) {
|
|
|
|
my $api = $1;
|
|
|
|
my $impl = $2;
|
|
|
|
|
|
|
|
if ($api ne "no" &&
|
|
|
|
$api ne "name" &&
|
|
|
|
$table ne "virStateDriver" &&
|
|
|
|
!exists $acls{$impl} &&
|
|
|
|
!exists $whitelist{$api} &&
|
|
|
|
!exists $implwhitelist{$impl}) {
|
|
|
|
print "$ARGV:$. Missing ACL check in function '$impl' for '$api'\n";
|
|
|
|
$status = 1;
|
|
|
|
}
|
2013-06-27 16:12:20 +00:00
|
|
|
|
|
|
|
if (exists $filtered{$api} &&
|
|
|
|
!exists $aclfilters{$impl} &&
|
|
|
|
!exists $filterimplwhitelist{$impl}) {
|
|
|
|
print "$ARGV:$. Missing ACL filter in function '$impl' for '$api'\n";
|
|
|
|
$status = 1;
|
|
|
|
}
|
2013-04-29 15:37:07 +00:00
|
|
|
}
|
|
|
|
} elsif (/^(?:static\s+)?(vir(?:\w+)?Driver)\s+/) {
|
|
|
|
if ($1 ne "virNWFilterCallbackDriver" &&
|
|
|
|
$1 ne "virNWFilterTechDriver" &&
|
|
|
|
$1 ne "virDomainConfNWFilterDriver") {
|
|
|
|
$intable = 1;
|
|
|
|
$table = $1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
my $count;
|
|
|
|
$count = s/{//g;
|
|
|
|
$brace += $count;
|
|
|
|
$count = s/}//g;
|
|
|
|
$brace -= $count;
|
2014-02-09 20:10:35 +00:00
|
|
|
} continue {
|
|
|
|
close ARGV if eof;
|
2013-04-29 15:37:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
exit $status;
|