xenstored: apply a write transaction rate limit

This avoids a rogue client being about to stall another client (eg the
toolstack) indefinitely.

This is XSA-206.

Signed-off-by: Ian Jackson <Ian.Jackson@eu.citrix.com>

Backported to 4.8 (not entirely trivial).

Reported-by: Juergen Gross <jgross@suse.com>
Signed-off-by: George Dunlap <george.dunlap@citrix.com>
Acked-by: Ian Jackson <Ian.Jackson@eu.citrix.com>
This commit is contained in:
Ian Jackson authored and Ian Jackson committed 2017-04-05 15:09:08 +01:00
1 parent e0354e65fe
commit 4cd02a2513
6 files changed
+262 -1

No files matched your search

+2 -1
View File
@@ -32,6 +32,7 @@ XENSTORED_OBJS_$(CONFIG_FreeBSD) = xenstored_posix.o
XENSTORED_OBJS_$(CONFIG_MiniOS) = xenstored_minios.o
XENSTORED_OBJS += $(XENSTORED_OBJS_y)
LDLIBS_xenstored += -lrt
ifneq ($(XENSTORE_STATIC_CLIENTS),y)
LIBXENSTORE := libxenstore.so
@@ -73,7 +74,7 @@ endif
$(XENSTORED_OBJS): CFLAGS += $(CFLAGS_libxengnttab)
xenstored: $(XENSTORED_OBJS)
$(CC) $^ $(LDFLAGS) $(LDLIBS_libxenevtchn) $(LDLIBS_libxengnttab) $(LDLIBS_libxenctrl) $(SOCKET_LIBS) -o $@ $(APPEND_LDFLAGS)
$(CC) $^ $(LDFLAGS) $(LDLIBS_libxenevtchn) $(LDLIBS_libxengnttab) $(LDLIBS_libxenctrl) $(LDLIBS_xenstored) $(SOCKET_LIBS) -o $@ $(APPEND_LDFLAGS)
xenstored.a: $(XENSTORED_OBJS)
$(AR) cr $@ $^
+9
View File
@@ -358,6 +358,7 @@ static void initialize_fds(int sock, int *p_sock_pollfd_idx,
int *ptimeout)
{
struct connection *conn;
struct wrl_timestampt now;
if (fds)
memset(fds, 0, sizeof(struct pollfd) * current_array_size);
@@ -377,8 +378,11 @@ static void initialize_fds(int sock, int *p_sock_pollfd_idx,
xce_pollfd_idx = set_fd(xenevtchn_fd(xce_handle),
POLLIN|POLLPRI);
wrl_gettime_now(&now);
list_for_each_entry(conn, &connections, list) {
if (conn->domain) {
wrl_check_timeout(conn->domain, now, ptimeout);
if (domain_can_read(conn) ||
(domain_can_write(conn) &&
!list_empty(&conn->out_list)))
@@ -833,6 +837,7 @@ static void delete_node_single(struct connection *conn, struct node *node)
corrupt(conn, "Could not delete '%s'", node->name);
return;
}
domain_entry_dec(conn, node);
}
@@ -972,6 +977,7 @@ static void do_write(struct connection *conn, struct buffered_data *in)
}
add_change_node(conn->transaction, name, false);
wrl_apply_debit_direct(conn);
fire_watches(conn, in, name, false);
send_ack(conn, XS_WRITE);
}
@@ -1003,6 +1009,7 @@ static void do_mkdir(struct connection *conn, struct buffered_data *in)
return;
}
add_change_node(conn->transaction, name, false);
wrl_apply_debit_direct(conn);
fire_watches(conn, in, name, false);
}
send_ack(conn, XS_MKDIR);
@@ -1129,6 +1136,7 @@ static void do_rm(struct connection *conn, struct buffered_data *in)
if (_rm(conn, node, name)) {
add_change_node(conn->transaction, name, true);
wrl_apply_debit_direct(conn);
fire_watches(conn, in, name, true);
send_ack(conn, XS_RM);
}
@@ -1205,6 +1213,7 @@ static void do_set_perms(struct connection *conn, struct buffered_data *in)
}
add_change_node(conn->transaction, name, false);
wrl_apply_debit_direct(conn);
fire_watches(conn, in, name, false);
send_ack(conn, XS_SET_PERMS);
}
+6
View File
@@ -33,6 +33,12 @@
#include "list.h"
#include "tdb.h"
#define MIN(a, b) (((a) < (b))? (a) : (b))
typedef int32_t wrl_creditt;
#define WRL_CREDIT_MAX (1000*1000*1000)
/* ^ satisfies non-overflow condition for wrl_xfer_credit */
struct buffered_data
{
struct list_head list;
+215
View File
@@ -21,6 +21,7 @@
#include <unistd.h>
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>
#include "utils.h"
#include "talloc.h"
@@ -74,6 +75,10 @@ struct domain
/* number of watch for this domain */
int nbwatch;
/* write rate limit */
wrl_creditt wrl_credit; /* [ -wrl_config_writecost, +_dburst ] */
struct wrl_timestampt wrl_timestamp;
};
static LIST_HEAD(domains);
@@ -206,6 +211,8 @@ static int destroy_domain(void *_domain)
fire_watches(NULL, domain, "@releaseDomain", false);
wrl_domain_destroy(domain);
return 0;
}
@@ -253,6 +260,9 @@ void handle_event(void)
bool domain_can_read(struct connection *conn)
{
struct xenstore_domain_interface *intf = conn->domain->interface;
if (domain_is_unprivileged(conn) && conn->domain->wrl_credit < 0)
return false;
return (intf->req_cons != intf->req_prod);
}
@@ -284,6 +294,8 @@ static struct domain *new_domain(void *context, unsigned int domid,
domain->domid = domid;
domain->path = talloc_domain_path(domain, domid);
wrl_domain_new(domain);
list_add(&domain->list, &domains);
talloc_set_destructor(domain, destroy_domain);
@@ -751,6 +763,209 @@ int domain_watch(struct connection *conn)
: 0;
}
static wrl_creditt wrl_config_writecost = WRL_FACTOR;
static wrl_creditt wrl_config_rate = WRL_RATE * WRL_FACTOR;
static wrl_creditt wrl_config_dburst = WRL_DBURST * WRL_FACTOR;
static wrl_creditt wrl_config_gburst = WRL_GBURST * WRL_FACTOR;
static wrl_creditt wrl_config_newdoms_dburst =
WRL_DBURST * WRL_NEWDOMS * WRL_FACTOR;
long wrl_ntransactions;
static long wrl_ndomains;
static wrl_creditt wrl_reserve; /* [-wrl_config_newdoms_dburst, +_gburst ] */
void wrl_gettime_now(struct wrl_timestampt *now_wt)
{
struct timespec now_ts;
int r;
r = clock_gettime(CLOCK_MONOTONIC, &now_ts);
if (r)
barf_perror("Could not find time (clock_gettime failed)");
now_wt->sec = now_ts.tv_sec;
now_wt->msec = now_ts.tv_nsec / 1000000;
}
static void wrl_xfer_credit(wrl_creditt *debit, wrl_creditt debit_floor,
wrl_creditt *credit, wrl_creditt credit_ceil)
/*
* Transfers zero or more credit from "debit" to "credit".
* Transfers as much as possible while maintaining
* debit >= debit_floor and credit <= credit_ceil.
* (If that's violated already, does nothing.)
*
* Sufficient conditions to avoid overflow, either of:
* |every argument| <= 0x3fffffff
* |every argument| <= 1E9
* |every argument| <= WRL_CREDIT_MAX
* (And this condition is preserved.)
*/
{
wrl_creditt xfer = MIN( *debit - debit_floor,
credit_ceil - *credit );
if (xfer > 0) {
*debit -= xfer;
*credit += xfer;
}
}
void wrl_domain_new(struct domain *domain)
{
domain->wrl_credit = 0;
wrl_gettime_now(&domain->wrl_timestamp);
wrl_ndomains++;
/* Steal up to DBURST from the reserve */
wrl_xfer_credit(&wrl_reserve, -wrl_config_newdoms_dburst,
&domain->wrl_credit, wrl_config_dburst);
}
void wrl_domain_destroy(struct domain *domain)
{
wrl_ndomains--;
/*
* Don't bother recalculating domain's credit - this just
* means we don't give the reserve the ending domain's credit
* for time elapsed since last update.
*/
wrl_xfer_credit(&domain->wrl_credit, 0,
&wrl_reserve, wrl_config_dburst);
}
void wrl_credit_update(struct domain *domain, struct wrl_timestampt now)
{
/*
* We want to calculate
* credit += (now - timestamp) * RATE / ndoms;
* But we want it to saturate, and to avoid floating point.
* To avoid rounding errors from constantly adding small
* amounts of credit, we only add credit for whole milliseconds.
*/
long seconds = now.sec - domain->wrl_timestamp.sec;
long milliseconds = now.msec - domain->wrl_timestamp.msec;
long msec;
int64_t denom, num;
wrl_creditt surplus;
seconds = MIN(seconds, 1000*1000); /* arbitrary, prevents overflow */
msec = seconds * 1000 + milliseconds;
if (msec < 0)
/* shouldn't happen with CLOCK_MONOTONIC */
msec = 0;
/* 32x32 -> 64 cannot overflow */
denom = (int64_t)msec * wrl_config_rate;
num = (int64_t)wrl_ndomains * 1000;
/* denom / num <= 1E6 * wrl_config_rate, so with
reasonable wrl_config_rate, denom / num << 2^64 */
/* at last! */
domain->wrl_credit = MIN( (int64_t)domain->wrl_credit + denom / num,
WRL_CREDIT_MAX );
/* (maybe briefly violating the DBURST cap on wrl_credit) */
/* maybe take from the reserve to make us nonnegative */
wrl_xfer_credit(&wrl_reserve, 0,
&domain->wrl_credit, 0);
/* return any surplus (over DBURST) to the reserve */
surplus = 0;
wrl_xfer_credit(&domain->wrl_credit, wrl_config_dburst,
&surplus, WRL_CREDIT_MAX);
wrl_xfer_credit(&surplus, 0,
&wrl_reserve, wrl_config_gburst);
/* surplus is now implicitly discarded */
domain->wrl_timestamp = now;
trace("wrl: dom %4d %6ld msec %9ld credit %9ld reserve"
" %9ld discard\n",
domain->domid,
msec,
(long)domain->wrl_credit, (long)wrl_reserve,
(long)surplus);
}
void wrl_check_timeout(struct domain *domain,
struct wrl_timestampt now,
int *ptimeout)
{
uint64_t num, denom;
int wakeup;
wrl_credit_update(domain, now);
if (domain->wrl_credit >= 0)
/* not blocked */
return;
if (!*ptimeout)
/* already decided on immediate wakeup,
so no need to calculate our timeout */
return;
/* calculate wakeup = now + -credit / (RATE / ndoms); */
/* credit cannot go more -ve than one transaction,
* so the first multiplication cannot overflow even 32-bit */
num = (uint64_t)(-domain->wrl_credit * 1000) * wrl_ndomains;
denom = wrl_config_rate;
wakeup = MIN( num / denom /* uint64_t */, INT_MAX );
if (*ptimeout==-1 || wakeup < *ptimeout)
*ptimeout = wakeup;
trace("wrl: domain %u credit=%ld (reserve=%ld) SLEEPING for %d\n",
domain->domid,
(long)domain->wrl_credit, (long)wrl_reserve,
wakeup);
}
void wrl_apply_debit_actual(struct domain *domain)
{
struct wrl_timestampt now;
if (!domain)
/* sockets escape the write rate limit */
return;
wrl_gettime_now(&now);
wrl_credit_update(domain, now);
domain->wrl_credit -= wrl_config_writecost;
trace("wrl: domain %u credit=%ld (reserve=%ld)\n",
domain->domid,
(long)domain->wrl_credit, (long)wrl_reserve);
}
void wrl_apply_debit_direct(struct connection *conn)
{
if (!conn)
/* some writes are generated internally */
return;
if (conn->transaction)
/* these are accounted for when the transaction ends */
return;
if (!wrl_ntransactions)
/* we don't conflict with anyone */
return;
wrl_apply_debit_actual(conn->domain);
}
void wrl_apply_debit_trans_commit(struct connection *conn)
{
if (wrl_ntransactions <= 1)
/* our own transaction appears in the counter */
return;
wrl_apply_debit_actual(conn->domain);
}
/*
* Local variables:
* c-file-style: "linux"
+25
View File
@@ -65,4 +65,29 @@ void domain_watch_inc(struct connection *conn);
void domain_watch_dec(struct connection *conn);
int domain_watch(struct connection *conn);
/* Write rate limiting */
#define WRL_FACTOR 1000 /* for fixed-point arithmetic */
#define WRL_RATE 200
#define WRL_DBURST 10
#define WRL_GBURST 1000
#define WRL_NEWDOMS 5
struct wrl_timestampt {
time_t sec;
int msec;
};
extern long wrl_ntransactions;
void wrl_gettime_now(struct wrl_timestampt *now_ts);
void wrl_domain_new(struct domain *domain);
void wrl_domain_destroy(struct domain *domain);
void wrl_credit_update(struct domain *domain, struct wrl_timestampt now);
void wrl_check_timeout(struct domain *domain,
struct wrl_timestampt now,
int *ptimeout);
void wrl_apply_debit_direct(struct connection *conn);
void wrl_apply_debit_trans_commit(struct connection *conn);
#endif /* _XENSTORED_DOMAIN_H */
+5
View File
@@ -120,6 +120,7 @@ static int destroy_transaction(void *_transaction)
{
struct transaction *trans = _transaction;
wrl_ntransactions--;
trace_destroy(trans, "transaction");
if (trans->tdb)
tdb_close(trans->tdb);
@@ -183,6 +184,7 @@ void do_transaction_start(struct connection *conn, struct buffered_data *in)
talloc_steal(conn, trans);
talloc_set_destructor(trans, destroy_transaction);
conn->transaction_started++;
wrl_ntransactions++;
snprintf(id_str, sizeof(id_str), "%u", trans->id);
send_reply(conn, XS_TRANSACTION_START, id_str, strlen(id_str)+1);
@@ -218,6 +220,9 @@ void do_transaction_end(struct connection *conn, struct buffered_data *in)
send_error(conn, EAGAIN);
return;
}
wrl_apply_debit_trans_commit(conn);
if (!replace_tdb(trans->tdb_name, trans->tdb)) {
send_error(conn, errno);
return;
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.