blob: 745ad5495096359cd8fa228bae178a89ebc60052 [file] [log] [blame]
/*
* Copyright (C) 2013-2015 Kay Sievers
* Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org>
* Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org>
* Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com>
* Copyright (C) 2013-2015 Linux Foundation
* Copyright (C) 2014-2015 Djalal Harouni <tixxdz@opendz.org>
*
* kdbus 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.
*/
#include <linux/ctype.h>
#include <linux/fs.h>
#include <linux/string.h>
#include "item.h"
#include "limits.h"
#include "util.h"
/*
* This verifies the string at position @str with size @size is properly
* zero-terminated and does not contain a 0-byte but at the end.
*/
static bool kdbus_str_valid(const char *str, size_t size)
{
return size > 0 && memchr(str, '\0', size) == str + size - 1;
}
/**
* kdbus_item_validate_name() - validate an item containing a name
* @item: Item to validate
*
* Return: zero on success or an negative error code on failure
*/
int kdbus_item_validate_name(const struct kdbus_item *item)
{
const char *name = item->str;
unsigned int i;
size_t len;
if (item->size < KDBUS_ITEM_HEADER_SIZE + 2)
return -EINVAL;
if (item->size > KDBUS_ITEM_HEADER_SIZE +
KDBUS_SYSNAME_MAX_LEN + 1)
return -ENAMETOOLONG;
if (!kdbus_str_valid(name, KDBUS_ITEM_PAYLOAD_SIZE(item)))
return -EINVAL;
len = strlen(name);
if (len == 0)
return -EINVAL;
for (i = 0; i < len; i++) {
if (isalpha(name[i]))
continue;
if (isdigit(name[i]))
continue;
if (name[i] == '_')
continue;
if (i > 0 && i + 1 < len && (name[i] == '-' || name[i] == '.'))
continue;
return -EINVAL;
}
return 0;
}
/**
* kdbus_item_validate() - validate a single item
* @item: item to validate
*
* Return: 0 if item is valid, negative error code if not.
*/
int kdbus_item_validate(const struct kdbus_item *item)
{
size_t payload_size = KDBUS_ITEM_PAYLOAD_SIZE(item);
size_t l;
int ret;
BUILD_BUG_ON(KDBUS_ITEM_HEADER_SIZE !=
sizeof(struct kdbus_item_header));
if (item->size < KDBUS_ITEM_HEADER_SIZE)
return -EINVAL;
switch (item->type) {
case KDBUS_ITEM_NEGOTIATE:
if (payload_size % sizeof(u64) != 0)
return -EINVAL;
break;
case KDBUS_ITEM_PAYLOAD_VEC:
if (payload_size != sizeof(struct kdbus_vec))
return -EINVAL;
if (item->vec.size == 0 || item->vec.size > SIZE_MAX)
return -EINVAL;
break;
case KDBUS_ITEM_PAYLOAD_OFF:
if (payload_size != sizeof(struct kdbus_vec))
return -EINVAL;
if (item->vec.size == 0 || item->vec.size > SIZE_MAX)
return -EINVAL;
break;
case KDBUS_ITEM_PAYLOAD_MEMFD:
if (payload_size != sizeof(struct kdbus_memfd))
return -EINVAL;
if (item->memfd.size == 0 || item->memfd.size > SIZE_MAX)
return -EINVAL;
if (item->memfd.fd < 0)
return -EBADF;
break;
case KDBUS_ITEM_FDS:
if (payload_size % sizeof(int) != 0)
return -EINVAL;
break;
case KDBUS_ITEM_CANCEL_FD:
if (payload_size != sizeof(int))
return -EINVAL;
break;
case KDBUS_ITEM_BLOOM_PARAMETER:
if (payload_size != sizeof(struct kdbus_bloom_parameter))
return -EINVAL;
break;
case KDBUS_ITEM_BLOOM_FILTER:
/* followed by the bloom-mask, depends on the bloom-size */
if (payload_size < sizeof(struct kdbus_bloom_filter))
return -EINVAL;
break;
case KDBUS_ITEM_BLOOM_MASK:
/* size depends on bloom-size of bus */
break;
case KDBUS_ITEM_CONN_DESCRIPTION:
case KDBUS_ITEM_MAKE_NAME:
ret = kdbus_item_validate_name(item);
if (ret < 0)
return ret;
break;
case KDBUS_ITEM_ATTACH_FLAGS_SEND:
case KDBUS_ITEM_ATTACH_FLAGS_RECV:
case KDBUS_ITEM_ID:
if (payload_size != sizeof(u64))
return -EINVAL;
break;
case KDBUS_ITEM_TIMESTAMP:
if (payload_size != sizeof(struct kdbus_timestamp))
return -EINVAL;
break;
case KDBUS_ITEM_CREDS:
if (payload_size != sizeof(struct kdbus_creds))
return -EINVAL;
break;
case KDBUS_ITEM_AUXGROUPS:
if (payload_size % sizeof(u32) != 0)
return -EINVAL;
break;
case KDBUS_ITEM_NAME:
case KDBUS_ITEM_DST_NAME:
case KDBUS_ITEM_PID_COMM:
case KDBUS_ITEM_TID_COMM:
case KDBUS_ITEM_EXE:
case KDBUS_ITEM_CMDLINE:
case KDBUS_ITEM_CGROUP:
case KDBUS_ITEM_SECLABEL:
if (!kdbus_str_valid(item->str, payload_size))
return -EINVAL;
break;
case KDBUS_ITEM_CAPS:
if (payload_size < sizeof(u32))
return -EINVAL;
if (payload_size < sizeof(u32) +
4 * CAP_TO_INDEX(item->caps.last_cap) * sizeof(u32))
return -EINVAL;
break;
case KDBUS_ITEM_AUDIT:
if (payload_size != sizeof(struct kdbus_audit))
return -EINVAL;
break;
case KDBUS_ITEM_POLICY_ACCESS:
if (payload_size != sizeof(struct kdbus_policy_access))
return -EINVAL;
break;
case KDBUS_ITEM_NAME_ADD:
case KDBUS_ITEM_NAME_REMOVE:
case KDBUS_ITEM_NAME_CHANGE:
if (payload_size < sizeof(struct kdbus_notify_name_change))
return -EINVAL;
l = payload_size - offsetof(struct kdbus_notify_name_change,
name);
if (l > 0 && !kdbus_str_valid(item->name_change.name, l))
return -EINVAL;
break;
case KDBUS_ITEM_ID_ADD:
case KDBUS_ITEM_ID_REMOVE:
if (payload_size != sizeof(struct kdbus_notify_id_change))
return -EINVAL;
break;
case KDBUS_ITEM_REPLY_TIMEOUT:
case KDBUS_ITEM_REPLY_DEAD:
if (payload_size != 0)
return -EINVAL;
break;
default:
break;
}
return 0;
}
/**
* kdbus_items_validate() - validate items passed by user-space
* @items: items to validate
* @items_size: number of items
*
* This verifies that the passed items pointer is consistent and valid.
* Furthermore, each item is checked for:
* - valid "size" value
* - payload is of expected type
* - payload is fully included in the item
* - string payloads are zero-terminated
*
* Return: 0 on success, negative error code on failure.
*/
int kdbus_items_validate(const struct kdbus_item *items, size_t items_size)
{
const struct kdbus_item *item;
int ret;
KDBUS_ITEMS_FOREACH(item, items, items_size) {
if (!KDBUS_ITEM_VALID(item, items, items_size))
return -EINVAL;
ret = kdbus_item_validate(item);
if (ret < 0)
return ret;
}
if (!KDBUS_ITEMS_END(item, items, items_size))
return -EINVAL;
return 0;
}
static struct kdbus_item *kdbus_items_get(const struct kdbus_item *items,
size_t items_size,
unsigned int item_type)
{
const struct kdbus_item *iter, *found = NULL;
KDBUS_ITEMS_FOREACH(iter, items, items_size) {
if (iter->type == item_type) {
if (found)
return ERR_PTR(-EEXIST);
found = iter;
}
}
return (struct kdbus_item *)found ? : ERR_PTR(-EBADMSG);
}
/**
* kdbus_items_get_str() - get string from a list of items
* @items: The items to walk
* @items_size: The size of all items
* @item_type: The item type to look for
*
* This function walks a list of items and searches for items of type
* @item_type. If it finds exactly one such item, @str_ret will be set to
* the .str member of the item.
*
* Return: the string, if the item was found exactly once, ERR_PTR(-EEXIST)
* if the item was found more than once, and ERR_PTR(-EBADMSG) if there was
* no item of the given type.
*/
const char *kdbus_items_get_str(const struct kdbus_item *items,
size_t items_size,
unsigned int item_type)
{
const struct kdbus_item *item;
item = kdbus_items_get(items, items_size, item_type);
return IS_ERR(item) ? ERR_CAST(item) : item->str;
}
/**
* kdbus_item_set() - Set item content
* @item: The item to modify
* @type: The item type to set (KDBUS_ITEM_*)
* @data: Data to copy to item->data, may be %NULL
* @len: Number of bytes in @data
*
* This sets type, size and data fields of an item. If @data is NULL, the data
* memory is cleared.
*
* Note that you must align your @data memory to 8 bytes. Trailing padding (in
* case @len is not 8byte aligned) is cleared by this call.
*
* Returns: Pointer to the following item.
*/
struct kdbus_item *kdbus_item_set(struct kdbus_item *item, u64 type,
const void *data, size_t len)
{
item->type = type;
item->size = KDBUS_ITEM_HEADER_SIZE + len;
if (data) {
memcpy(item->data, data, len);
memset(item->data + len, 0, KDBUS_ALIGN8(len) - len);
} else {
memset(item->data, 0, KDBUS_ALIGN8(len));
}
return KDBUS_ITEM_NEXT(item);
}