mirror of
https://github.com/torvalds/linux.git
synced 2024-12-20 18:11:47 +00:00
11924ba5e6
When adding a VMCI resource, the check for an existing entry
would ignore that the new entry could be a wildcard. This could
result in multiple resource entries that would match a given
handle. One disastrous outcome of this is that the
refcounting used to ensure that delayed callbacks for VMCI
datagrams have run before the datagram is destroyed can be
wrong, since the refcount could be increased on the duplicate
entry. This in turn leads to a use after free bug. This issue
was discovered by Hangbin Liu using KASAN and syzkaller.
Fixes: bc63dedb7d
("VMCI: resource object implementation")
Reported-by: Hangbin Liu <liuhangbin@gmail.com>
Reviewed-by: Adit Ranadive <aditr@vmware.com>
Reviewed-by: Vishnu Dasa <vdasa@vmware.com>
Signed-off-by: Jorgen Hansen <jhansen@vmware.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
230 lines
5.7 KiB
C
230 lines
5.7 KiB
C
/*
|
|
* VMware VMCI Driver
|
|
*
|
|
* Copyright (C) 2012 VMware, Inc. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation version 2 and no later version.
|
|
*
|
|
* This program 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 General Public License
|
|
* for more details.
|
|
*/
|
|
|
|
#include <linux/vmw_vmci_defs.h>
|
|
#include <linux/hash.h>
|
|
#include <linux/types.h>
|
|
#include <linux/rculist.h>
|
|
#include <linux/completion.h>
|
|
|
|
#include "vmci_resource.h"
|
|
#include "vmci_driver.h"
|
|
|
|
|
|
#define VMCI_RESOURCE_HASH_BITS 7
|
|
#define VMCI_RESOURCE_HASH_BUCKETS (1 << VMCI_RESOURCE_HASH_BITS)
|
|
|
|
struct vmci_hash_table {
|
|
spinlock_t lock;
|
|
struct hlist_head entries[VMCI_RESOURCE_HASH_BUCKETS];
|
|
};
|
|
|
|
static struct vmci_hash_table vmci_resource_table = {
|
|
.lock = __SPIN_LOCK_UNLOCKED(vmci_resource_table.lock),
|
|
};
|
|
|
|
static unsigned int vmci_resource_hash(struct vmci_handle handle)
|
|
{
|
|
return hash_32(handle.resource, VMCI_RESOURCE_HASH_BITS);
|
|
}
|
|
|
|
/*
|
|
* Gets a resource (if one exists) matching given handle from the hash table.
|
|
*/
|
|
static struct vmci_resource *vmci_resource_lookup(struct vmci_handle handle,
|
|
enum vmci_resource_type type)
|
|
{
|
|
struct vmci_resource *r, *resource = NULL;
|
|
unsigned int idx = vmci_resource_hash(handle);
|
|
|
|
rcu_read_lock();
|
|
hlist_for_each_entry_rcu(r,
|
|
&vmci_resource_table.entries[idx], node) {
|
|
u32 cid = r->handle.context;
|
|
u32 rid = r->handle.resource;
|
|
|
|
if (r->type == type &&
|
|
rid == handle.resource &&
|
|
(cid == handle.context || cid == VMCI_INVALID_ID ||
|
|
handle.context == VMCI_INVALID_ID)) {
|
|
resource = r;
|
|
break;
|
|
}
|
|
}
|
|
rcu_read_unlock();
|
|
|
|
return resource;
|
|
}
|
|
|
|
/*
|
|
* Find an unused resource ID and return it. The first
|
|
* VMCI_RESERVED_RESOURCE_ID_MAX are reserved so we start from
|
|
* its value + 1.
|
|
* Returns VMCI resource id on success, VMCI_INVALID_ID on failure.
|
|
*/
|
|
static u32 vmci_resource_find_id(u32 context_id,
|
|
enum vmci_resource_type resource_type)
|
|
{
|
|
static u32 resource_id = VMCI_RESERVED_RESOURCE_ID_MAX + 1;
|
|
u32 old_rid = resource_id;
|
|
u32 current_rid;
|
|
|
|
/*
|
|
* Generate a unique resource ID. Keep on trying until we wrap around
|
|
* in the RID space.
|
|
*/
|
|
do {
|
|
struct vmci_handle handle;
|
|
|
|
current_rid = resource_id;
|
|
resource_id++;
|
|
if (unlikely(resource_id == VMCI_INVALID_ID)) {
|
|
/* Skip the reserved rids. */
|
|
resource_id = VMCI_RESERVED_RESOURCE_ID_MAX + 1;
|
|
}
|
|
|
|
handle = vmci_make_handle(context_id, current_rid);
|
|
if (!vmci_resource_lookup(handle, resource_type))
|
|
return current_rid;
|
|
} while (resource_id != old_rid);
|
|
|
|
return VMCI_INVALID_ID;
|
|
}
|
|
|
|
|
|
int vmci_resource_add(struct vmci_resource *resource,
|
|
enum vmci_resource_type resource_type,
|
|
struct vmci_handle handle)
|
|
|
|
{
|
|
unsigned int idx;
|
|
int result;
|
|
|
|
spin_lock(&vmci_resource_table.lock);
|
|
|
|
if (handle.resource == VMCI_INVALID_ID) {
|
|
handle.resource = vmci_resource_find_id(handle.context,
|
|
resource_type);
|
|
if (handle.resource == VMCI_INVALID_ID) {
|
|
result = VMCI_ERROR_NO_HANDLE;
|
|
goto out;
|
|
}
|
|
} else if (vmci_resource_lookup(handle, resource_type)) {
|
|
result = VMCI_ERROR_ALREADY_EXISTS;
|
|
goto out;
|
|
}
|
|
|
|
resource->handle = handle;
|
|
resource->type = resource_type;
|
|
INIT_HLIST_NODE(&resource->node);
|
|
kref_init(&resource->kref);
|
|
init_completion(&resource->done);
|
|
|
|
idx = vmci_resource_hash(resource->handle);
|
|
hlist_add_head_rcu(&resource->node, &vmci_resource_table.entries[idx]);
|
|
|
|
result = VMCI_SUCCESS;
|
|
|
|
out:
|
|
spin_unlock(&vmci_resource_table.lock);
|
|
return result;
|
|
}
|
|
|
|
void vmci_resource_remove(struct vmci_resource *resource)
|
|
{
|
|
struct vmci_handle handle = resource->handle;
|
|
unsigned int idx = vmci_resource_hash(handle);
|
|
struct vmci_resource *r;
|
|
|
|
/* Remove resource from hash table. */
|
|
spin_lock(&vmci_resource_table.lock);
|
|
|
|
hlist_for_each_entry(r, &vmci_resource_table.entries[idx], node) {
|
|
if (vmci_handle_is_equal(r->handle, resource->handle)) {
|
|
hlist_del_init_rcu(&r->node);
|
|
break;
|
|
}
|
|
}
|
|
|
|
spin_unlock(&vmci_resource_table.lock);
|
|
synchronize_rcu();
|
|
|
|
vmci_resource_put(resource);
|
|
wait_for_completion(&resource->done);
|
|
}
|
|
|
|
struct vmci_resource *
|
|
vmci_resource_by_handle(struct vmci_handle resource_handle,
|
|
enum vmci_resource_type resource_type)
|
|
{
|
|
struct vmci_resource *r, *resource = NULL;
|
|
|
|
rcu_read_lock();
|
|
|
|
r = vmci_resource_lookup(resource_handle, resource_type);
|
|
if (r &&
|
|
(resource_type == r->type ||
|
|
resource_type == VMCI_RESOURCE_TYPE_ANY)) {
|
|
resource = vmci_resource_get(r);
|
|
}
|
|
|
|
rcu_read_unlock();
|
|
|
|
return resource;
|
|
}
|
|
|
|
/*
|
|
* Get a reference to given resource.
|
|
*/
|
|
struct vmci_resource *vmci_resource_get(struct vmci_resource *resource)
|
|
{
|
|
kref_get(&resource->kref);
|
|
|
|
return resource;
|
|
}
|
|
|
|
static void vmci_release_resource(struct kref *kref)
|
|
{
|
|
struct vmci_resource *resource =
|
|
container_of(kref, struct vmci_resource, kref);
|
|
|
|
/* Verify the resource has been unlinked from hash table */
|
|
WARN_ON(!hlist_unhashed(&resource->node));
|
|
|
|
/* Signal that container of this resource can now be destroyed */
|
|
complete(&resource->done);
|
|
}
|
|
|
|
/*
|
|
* Resource's release function will get called if last reference.
|
|
* If it is the last reference, then we are sure that nobody else
|
|
* can increment the count again (it's gone from the resource hash
|
|
* table), so there's no need for locking here.
|
|
*/
|
|
int vmci_resource_put(struct vmci_resource *resource)
|
|
{
|
|
/*
|
|
* We propagate the information back to caller in case it wants to know
|
|
* whether entry was freed.
|
|
*/
|
|
return kref_put(&resource->kref, vmci_release_resource) ?
|
|
VMCI_SUCCESS_ENTRY_DEAD : VMCI_SUCCESS;
|
|
}
|
|
|
|
struct vmci_handle vmci_resource_handle(struct vmci_resource *resource)
|
|
{
|
|
return resource->handle;
|
|
}
|