mirror of
https://github.com/xen-project/xen.git
synced 2026-10-28 02:01:24 +01:00
Due to a typo the state record for a watch specifying a depth is
written only partially. Instead of using the correct length of the
xs_state_watch_ext structure only the length of the xs_state_watch
structure was used when writing the data.
Fix that by using the correct length.
This is XSA-514 / CVE-2026-79607
Fixes: db98de120c ("tools/xenstored: add depth information to watches")
Reported-by: Andrii Sultanov <andriy.sultanov@vates.tech>
Signed-off-by: Juergen Gross <jgross@suse.com>
423 lines
10 KiB
C
423 lines
10 KiB
C
/*
|
|
Watch code for Xen Store Daemon.
|
|
Copyright (C) 2005 Rusty Russell IBM Corporation
|
|
|
|
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; either version 2 of the License, or
|
|
(at your option) any 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.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <sys/types.h>
|
|
#include <stdarg.h>
|
|
#include <stdlib.h>
|
|
#include <sys/time.h>
|
|
#include <time.h>
|
|
#include <assert.h>
|
|
#include "talloc.h"
|
|
#include "list.h"
|
|
#include "watch.h"
|
|
#include "xenstore_lib.h"
|
|
#include "utils.h"
|
|
#include "domain.h"
|
|
#include "transaction.h"
|
|
|
|
struct watch
|
|
{
|
|
/* Watches on this connection */
|
|
struct list_head list;
|
|
|
|
/* Offset into path for skipping prefix (used for relative paths). */
|
|
unsigned int prefix_len;
|
|
|
|
int depth; /* -1: no depth limit. */
|
|
char *token;
|
|
char *node;
|
|
};
|
|
|
|
/* Is child a subnode of parent, or equal? */
|
|
static bool is_child(const char *child, const char *parent, int depth)
|
|
{
|
|
unsigned int len = strlen(parent); /* len == 1 if parent is "/". */
|
|
unsigned int sub_levels = (len == 1) ? 1 : 0;
|
|
|
|
if (strncmp(child, parent, len) != 0)
|
|
return false;
|
|
|
|
if (child[len] != '/' && child[len] != '\0' && len > 1)
|
|
return false;
|
|
|
|
if (depth < 0 || child[len] == '\0')
|
|
return true;
|
|
|
|
while (sub_levels <= depth) {
|
|
if (child[len] == '\0')
|
|
return true;
|
|
if (child[len] == '/')
|
|
sub_levels++;
|
|
len++;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static const char *get_watch_path(const struct watch *watch, const char *name)
|
|
{
|
|
return name + watch->prefix_len;
|
|
}
|
|
|
|
/*
|
|
* Check permissions of a specific watch to fire:
|
|
* Either the node itself or its parent have to be readable by the connection
|
|
* the watch has been setup for. In case a watch event is created due to
|
|
* changed permissions we need to take the old permissions into account, too.
|
|
*/
|
|
static bool watch_permitted(struct connection *conn, const void *ctx,
|
|
const char *name, const struct node *node,
|
|
struct node_perms *perms)
|
|
{
|
|
unsigned int perm;
|
|
const struct node *parent;
|
|
char *parent_name;
|
|
|
|
if (perms) {
|
|
perm = perm_for_conn(conn, perms);
|
|
if (perm & XS_PERM_READ)
|
|
return true;
|
|
}
|
|
|
|
if (!node) {
|
|
node = read_node_const(conn, ctx, name);
|
|
if (!node)
|
|
return false;
|
|
}
|
|
|
|
perm = perm_for_conn_from_node(conn, node);
|
|
if (perm & XS_PERM_READ)
|
|
return true;
|
|
|
|
parent = node->parent;
|
|
if (!parent) {
|
|
parent_name = get_parent(ctx, node->name);
|
|
if (!parent_name)
|
|
return false;
|
|
parent = read_node_const(conn, ctx, parent_name);
|
|
if (!parent)
|
|
return false;
|
|
}
|
|
|
|
perm = perm_for_conn_from_node(conn, parent);
|
|
|
|
return perm & XS_PERM_READ;
|
|
}
|
|
|
|
/*
|
|
* Check whether any watch events are to be sent.
|
|
* Temporary memory allocations are done with ctx.
|
|
* We need to take the (potential) old permissions of the node into account
|
|
* as a watcher losing permissions to access a node should receive the
|
|
* watch event, too.
|
|
*/
|
|
void fire_watches(struct connection *conn, const void *ctx, const char *name,
|
|
const struct node *node, enum watch_match match,
|
|
struct node_perms *perms)
|
|
{
|
|
struct connection *i;
|
|
struct buffered_data *req;
|
|
struct watch *watch;
|
|
|
|
/* During transactions, don't fire watches, but queue them. */
|
|
if (conn && conn->transaction) {
|
|
queue_watches(conn, name, match);
|
|
return;
|
|
}
|
|
|
|
req = domain_is_unprivileged(conn) ? conn->in : NULL;
|
|
|
|
/* Create an event for each watch. */
|
|
list_for_each_entry(i, &connections, list) {
|
|
if (!watch_permitted(i, ctx, name, node, perms))
|
|
continue;
|
|
|
|
list_for_each_entry(watch, &i->watches, list) {
|
|
bool send = false;
|
|
|
|
switch (match) {
|
|
case MATCH_EXACT:
|
|
send = streq(name, watch->node);
|
|
break;
|
|
|
|
case MATCH_SUBTREE:
|
|
send = is_child(name, watch->node,
|
|
watch->depth);
|
|
break;
|
|
|
|
case MATCH_DEPTH:
|
|
send = streq(name, watch->node) ||
|
|
(watch->depth > 0 &&
|
|
is_child(name, watch->node,
|
|
watch->depth));
|
|
break;
|
|
|
|
case MATCH_NODEPTH:
|
|
send = streq(name, watch->node) &&
|
|
watch->depth < 0;
|
|
break;
|
|
}
|
|
|
|
if (send)
|
|
send_event(req, i, get_watch_path(watch, name),
|
|
watch->token);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int destroy_watch(void *_watch)
|
|
{
|
|
trace_destroy(_watch, "watch");
|
|
return 0;
|
|
}
|
|
|
|
static int check_watch_path(struct connection *conn, const void *ctx,
|
|
const char **path, bool *relative)
|
|
{
|
|
*relative = !strstarts(*path, "/") && !strstarts(*path, "@");
|
|
*path = canonicalize(conn, ctx, *path, true);
|
|
|
|
return *path ? 0 : errno;
|
|
}
|
|
|
|
static struct watch *add_watch(struct connection *conn, const char *path,
|
|
const char *token, int depth, bool relative,
|
|
bool no_quota_check)
|
|
{
|
|
struct watch *watch;
|
|
|
|
watch = talloc(conn, struct watch);
|
|
if (!watch)
|
|
goto nomem;
|
|
watch->depth = depth;
|
|
watch->node = talloc_strdup(watch, path);
|
|
watch->token = talloc_strdup(watch, token);
|
|
if (!watch->node || !watch->token)
|
|
goto nomem;
|
|
if (domain_memory_add(conn, conn->id, strlen(path) + strlen(token),
|
|
no_quota_check))
|
|
goto nomem;
|
|
|
|
watch->prefix_len = relative ? strlen(get_implicit_path(conn)) + 1 : 0;
|
|
|
|
domain_watch_inc(conn);
|
|
list_add_tail(&watch->list, &conn->watches);
|
|
talloc_set_destructor(watch, destroy_watch);
|
|
|
|
return watch;
|
|
|
|
nomem:
|
|
talloc_free(watch);
|
|
errno = ENOMEM;
|
|
return NULL;
|
|
}
|
|
|
|
int do_watch(const void *ctx, struct connection *conn, struct buffered_data *in)
|
|
{
|
|
struct watch *watch;
|
|
const char *vec[3];
|
|
unsigned int n_pars;
|
|
int depth = -1;
|
|
bool relative;
|
|
|
|
n_pars = get_strings(in, vec, ARRAY_SIZE(vec));
|
|
if (n_pars == 3 &&
|
|
feature_available(conn, XENSTORE_SERVER_FEATURE_WATCHDEPTH)) {
|
|
depth = atoi(vec[2]);
|
|
if (depth < 0)
|
|
return EINVAL;
|
|
} else if (n_pars != 2)
|
|
return EINVAL;
|
|
|
|
errno = check_watch_path(conn, ctx, &(vec[0]), &relative);
|
|
if (errno)
|
|
return errno;
|
|
|
|
/* Check for duplicates. */
|
|
list_for_each_entry(watch, &conn->watches, list) {
|
|
if (streq(watch->node, vec[0]) &&
|
|
streq(watch->token, vec[1]))
|
|
return EEXIST;
|
|
}
|
|
|
|
if (domain_quota_add_exceeds(conn->domain, ACC_WATCH, 1))
|
|
return ENOSPC;
|
|
|
|
watch = add_watch(conn, vec[0], vec[1], depth, relative, false);
|
|
if (!watch)
|
|
return errno;
|
|
|
|
trace_create(watch, "watch");
|
|
send_ack(conn, XS_WATCH);
|
|
|
|
/*
|
|
* We fire once up front: simplifies clients and restart.
|
|
* This event will not be linked to the XS_WATCH request.
|
|
*/
|
|
send_event(NULL, conn, get_watch_path(watch, watch->node),
|
|
watch->token);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int do_unwatch(const void *ctx, struct connection *conn,
|
|
struct buffered_data *in)
|
|
{
|
|
struct watch *watch;
|
|
const char *node;
|
|
const char *vec[2];
|
|
|
|
if (get_strings(in, vec, ARRAY_SIZE(vec)) != ARRAY_SIZE(vec))
|
|
return EINVAL;
|
|
|
|
node = canonicalize(conn, ctx, vec[0], true);
|
|
if (!node)
|
|
return errno;
|
|
list_for_each_entry(watch, &conn->watches, list) {
|
|
if (streq(watch->node, node) && streq(watch->token, vec[1])) {
|
|
list_del(&watch->list);
|
|
domain_memory_add_nochk(conn, conn->id,
|
|
-strlen(watch->node) -
|
|
strlen(watch->token));
|
|
talloc_free(watch);
|
|
domain_watch_dec(conn);
|
|
send_ack(conn, XS_UNWATCH);
|
|
return 0;
|
|
}
|
|
}
|
|
return ENOENT;
|
|
}
|
|
|
|
void conn_delete_all_watches(struct connection *conn)
|
|
{
|
|
struct watch *watch;
|
|
|
|
while ((watch = list_top(&conn->watches, struct watch, list))) {
|
|
list_del(&watch->list);
|
|
domain_memory_add_nochk(conn, conn->id, -strlen(watch->node) -
|
|
strlen(watch->token));
|
|
talloc_free(watch);
|
|
domain_watch_dec(conn);
|
|
}
|
|
}
|
|
|
|
const char *dump_state_watches(FILE *fp, struct connection *conn,
|
|
unsigned int conn_id)
|
|
{
|
|
const char *ret = NULL;
|
|
struct watch *watch;
|
|
struct xs_state_watch sw;
|
|
struct xs_state_watch_ext swe;
|
|
struct xs_state_record_header head;
|
|
const char *path;
|
|
size_t path_len, token_len;
|
|
|
|
list_for_each_entry(watch, &conn->watches, list) {
|
|
path = get_watch_path(watch, watch->node);
|
|
path_len = strlen(path) + 1;
|
|
token_len = strlen(watch->token) + 1;
|
|
|
|
if (watch->depth >= 0) {
|
|
head.type = XS_STATE_TYPE_WATCH_EXT;
|
|
head.length = sizeof(swe);
|
|
swe.conn_id = conn_id;
|
|
swe.path_length = path_len;
|
|
swe.token_length = token_len;
|
|
swe.depth = watch->depth;
|
|
} else {
|
|
head.type = XS_STATE_TYPE_WATCH;
|
|
head.length = sizeof(sw);
|
|
sw.conn_id = conn_id;
|
|
sw.path_length = path_len;
|
|
sw.token_length = token_len;
|
|
}
|
|
|
|
head.length += path_len + token_len;
|
|
head.length = ROUNDUP(head.length, 8);
|
|
if (fwrite(&head, sizeof(head), 1, fp) != 1)
|
|
return "Dump watch state error";
|
|
|
|
if (watch->depth >= 0) {
|
|
if (fwrite(&swe, sizeof(swe), 1, fp) != 1)
|
|
return "Dump watch state ext error";
|
|
} else {
|
|
if (fwrite(&sw, sizeof(sw), 1, fp) != 1)
|
|
return "Dump watch state error";
|
|
}
|
|
|
|
if (fwrite(path, path_len, 1, fp) != 1)
|
|
return "Dump watch path error";
|
|
if (fwrite(watch->token, token_len, 1, fp) != 1)
|
|
return "Dump watch token error";
|
|
|
|
ret = dump_state_align(fp);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void process_state_watch(const void *ctx, unsigned int conn_id,
|
|
const char *path, const char *token,
|
|
int depth)
|
|
{
|
|
struct connection *conn;
|
|
bool relative;
|
|
|
|
conn = get_connection_by_id(conn_id);
|
|
if (!conn)
|
|
barf("connection not found for read watch");
|
|
|
|
/* Don't check success, we want the relative information only. */
|
|
check_watch_path(conn, ctx, &path, &relative);
|
|
if (!path)
|
|
barf("allocation error for read watch");
|
|
|
|
if (!add_watch(conn, path, token, depth, relative, true))
|
|
barf("error adding watch");
|
|
|
|
}
|
|
|
|
void read_state_watch(const void *ctx, const void *state)
|
|
{
|
|
const struct xs_state_watch *sw = state;
|
|
|
|
process_state_watch(ctx, sw->conn_id, (char *)sw->data,
|
|
(char *)sw->data + sw->path_length, -1);
|
|
}
|
|
|
|
void read_state_watch_ext(const void *ctx, const void *state)
|
|
{
|
|
const struct xs_state_watch_ext *swe = state;
|
|
|
|
process_state_watch(ctx, swe->conn_id, (char *)swe->data,
|
|
(char *)swe->data + swe->path_length, swe->depth);
|
|
}
|
|
|
|
/*
|
|
* Local variables:
|
|
* mode: C
|
|
* c-file-style: "linux"
|
|
* indent-tabs-mode: t
|
|
* c-basic-offset: 8
|
|
* tab-width: 8
|
|
* End:
|
|
*/
|