summaryrefslogtreecommitdiff
path: root/system/xen/xsa/xsa206-4.8-0001-xenstored-apply-a-write-transaction-rate-limit.patch
diff options
context:
space:
mode:
Diffstat (limited to 'system/xen/xsa/xsa206-4.8-0001-xenstored-apply-a-write-transaction-rate-limit.patch')
-rw-r--r--system/xen/xsa/xsa206-4.8-0001-xenstored-apply-a-write-transaction-rate-limit.patch456
1 files changed, 456 insertions, 0 deletions
diff --git a/system/xen/xsa/xsa206-4.8-0001-xenstored-apply-a-write-transaction-rate-limit.patch b/system/xen/xsa/xsa206-4.8-0001-xenstored-apply-a-write-transaction-rate-limit.patch
new file mode 100644
index 0000000000..ee39918cb7
--- /dev/null
+++ b/system/xen/xsa/xsa206-4.8-0001-xenstored-apply-a-write-transaction-rate-limit.patch
@@ -0,0 +1,456 @@
+From 8b2d563e6693527e0747fbb22c5f01eeb89a6c53 Mon Sep 17 00:00:00 2001
+From: Ian Jackson <ian.jackson@eu.citrix.com>
+Date: Tue, 7 Mar 2017 16:09:12 +0000
+Subject: [PATCH 01/15] 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>
+---
+ tools/xenstore/Makefile | 3 +-
+ tools/xenstore/xenstored_core.c | 9 ++
+ tools/xenstore/xenstored_core.h | 6 +
+ tools/xenstore/xenstored_domain.c | 215 +++++++++++++++++++++++++++++++++
+ tools/xenstore/xenstored_domain.h | 25 ++++
+ tools/xenstore/xenstored_transaction.c | 5 +
+ 6 files changed, 262 insertions(+), 1 deletion(-)
+
+diff --git a/tools/xenstore/Makefile b/tools/xenstore/Makefile
+index 36b6fd4..9cb54de 100644
+--- a/tools/xenstore/Makefile
++++ b/tools/xenstore/Makefile
+@@ -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 $@ $^
+diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
+index 3df977b..d14f096 100644
+--- a/tools/xenstore/xenstored_core.c
++++ b/tools/xenstore/xenstored_core.c
+@@ -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);
+ }
+diff --git a/tools/xenstore/xenstored_core.h b/tools/xenstore/xenstored_core.h
+index ecc614f..9e9d960 100644
+--- a/tools/xenstore/xenstored_core.h
++++ b/tools/xenstore/xenstored_core.h
+@@ -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;
+diff --git a/tools/xenstore/xenstored_domain.c b/tools/xenstore/xenstored_domain.c
+index 5de93d4..012dfe6 100644
+--- a/tools/xenstore/xenstored_domain.c
++++ b/tools/xenstore/xenstored_domain.c
+@@ -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"
+diff --git a/tools/xenstore/xenstored_domain.h b/tools/xenstore/xenstored_domain.h
+index 2554423..cec341e 100644
+--- a/tools/xenstore/xenstored_domain.h
++++ b/tools/xenstore/xenstored_domain.h
+@@ -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 */
+diff --git a/tools/xenstore/xenstored_transaction.c b/tools/xenstore/xenstored_transaction.c
+index 84cb0bf..5059a11 100644
+--- a/tools/xenstore/xenstored_transaction.c
++++ b/tools/xenstore/xenstored_transaction.c
+@@ -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;
+--
+2.1.4
+