summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Wilson <chris@chris-wilson.co.uk>2015-02-11 09:48:00 +0000
committerChris Wilson <chris@chris-wilson.co.uk>2015-02-11 09:51:43 +0000
commit426a3afcc6ec96b8d4bb93262557bd036e00718c (patch)
tree8bf7ca1d64f98e3328442178ea40774d52c0c50b
parent0b7a6666f82b4fa07f9c9d9a9c1819efc363b31b (diff)
sna/present: Prevent DoS from exhausting the kernel vblank queue
The kernel caps the number of vblanks per client to 128. There are already reports from the wild that Present clients are running foul of that limit, so supplement it with a timer fallback. Testcase: present-test with future flips Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk>
-rw-r--r--configure.ac2
-rw-r--r--src/sna/sna_present.c70
-rw-r--r--test/present-test.c139
3 files changed, 197 insertions, 14 deletions
diff --git a/configure.ac b/configure.ac
index afe162d4..7476e2b5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -270,7 +270,7 @@ if test "x$shm" = "xyes"; then
AC_DEFINE([HAVE_MIT_SHM], 1, [Define to 1 if MIT-SHM is available])
fi
-PKG_CHECK_MODULES(X11_DRI3, [xcb-dri3 xcb-sync xcb-present x11-xcb xshmfence x11 xrender xext libdrm], [x11_dri3="yes"], [x11_dri3="no"])
+PKG_CHECK_MODULES(X11_DRI3, [xcb-dri3 xcb-sync xcb-xfixes xcb-present x11-xcb xshmfence x11 xrender xext libdrm], [x11_dri3="yes"], [x11_dri3="no"])
AM_CONDITIONAL(X11_DRI3, test "x$x11_dri3" = "xyes" -a "x$shm" = "xyes")
AM_CONDITIONAL(X11_SHM, test "x$shm" = "xyes")
diff --git a/src/sna/sna_present.c b/src/sna/sna_present.c
index c2a9c5db..25b4e8bf 100644
--- a/src/sna/sna_present.c
+++ b/src/sna/sna_present.c
@@ -38,9 +38,10 @@
static present_screen_info_rec present_info;
struct sna_present_event {
- uint64_t event_id;
xf86CrtcPtr crtc;
struct sna *sna;
+ uint64_t event_id;
+ uint64_t target_msc;
};
static void sna_present_unflip(ScreenPtr screen, uint64_t event_id);
@@ -77,6 +78,56 @@ static inline int sna_wait_vblank(struct sna *sna, union drm_wait_vblank *vbl, i
return drmIoctl(sna->kgem.fd, DRM_IOCTL_WAIT_VBLANK, vbl);
}
+static uint32_t msc_to_delay(xf86CrtcPtr crtc, uint64_t msc)
+{
+ DisplayModePtr mode = &crtc->desiredMode;
+ return msc * mode->VTotal * mode->HTotal / mode->Clock;
+}
+
+static CARD32 sna_fake_vblank_handler(OsTimerPtr timer, CARD32 now, void *data)
+{
+ struct sna_present_event *info = data;
+ union drm_wait_vblank vbl;
+ uint64_t msc, ust;
+
+ DBG(("%s(now=%d)\n", __FUNCTION__, now));
+
+ VG_CLEAR(vbl);
+ vbl.request.type = DRM_VBLANK_RELATIVE;
+ vbl.request.sequence = 0;
+ if (sna_wait_vblank(info->sna, &vbl, sna_crtc_to_pipe(info->crtc)) == 0) {
+ ust = ust64(vbl.reply.tval_sec, vbl.reply.tval_usec);
+ msc = sna_crtc_record_vblank(info->crtc, &vbl);
+ if (msc < info->target_msc)
+ return msc_to_delay(info->crtc, msc - info->target_msc);
+ } else {
+ const struct ust_msc *swap = sna_crtc_last_swap(info->crtc);
+ ust = ust64(swap->tv_sec, swap->tv_usec);
+ msc = swap->msc;
+ }
+
+ present_event_notify(info->event_id, ust, msc);
+ free(info);
+ free(timer);
+ return 0;
+}
+
+static bool sna_fake_vblank(struct sna_present_event *event)
+{
+ uint64_t msc = sna_crtc_last_swap(event->crtc)->msc;
+ uint32_t delay;
+
+ if (msc < event->target_msc)
+ delay = msc_to_delay(event->crtc, event->target_msc - msc);
+ else
+ delay = 0;
+
+ DBG(("%s(target_msc=%lld, msc=%lld, delay=%ums)\n",
+ __FUNCTION__, event->target_msc, msc, delay));
+
+ return TimerSet(NULL, 0, delay, sna_fake_vblank_handler, event);
+}
+
static RRCrtcPtr
sna_present_get_crtc(WindowPtr window)
{
@@ -131,10 +182,11 @@ sna_present_vblank_handler(struct drm_event_vblank *event)
{
struct sna_present_event *info = to_present_event(event->user_data);
- DBG(("%s: pipe=%d tv=%d.%06d msc=%d, event %lld complete\n", __FUNCTION__,
+ DBG(("%s: pipe=%d tv=%d.%06d msc=%d, event %lld complete: target_msc=%lld\n", __FUNCTION__,
sna_crtc_to_pipe(info->crtc),
event->tv_sec, event->tv_usec, event->sequence,
- (long long)info->event_id));
+ (long long)info->event_id,
+ (long long)info->target_msc));
present_event_notify(info->event_id,
ust64(event->tv_sec, event->tv_usec),
sna_crtc_record_event(info->crtc, event));
@@ -156,9 +208,10 @@ sna_present_queue_vblank(RRCrtcPtr crtc, uint64_t event_id, uint64_t msc)
if (event == NULL)
return BadAlloc;
- event->event_id = event_id;
event->crtc = crtc->devPrivate;
event->sna = sna;
+ event->target_msc = msc;
+ event->event_id = event_id;
VG_CLEAR(vbl);
vbl.request.type = DRM_VBLANK_ABSOLUTE | DRM_VBLANK_EVENT;
@@ -166,8 +219,10 @@ sna_present_queue_vblank(RRCrtcPtr crtc, uint64_t event_id, uint64_t msc)
vbl.request.signal = (uintptr_t)MARK_PRESENT(event);
if (sna_wait_vblank(sna, &vbl, sna_crtc_to_pipe(event->crtc))) {
DBG(("%s: vblank enqueue failed\n", __FUNCTION__));
- free(event);
- return BadMatch;
+ if (!sna_fake_vblank(event)) {
+ free(event);
+ return BadAlloc;
+ }
}
return Success;
@@ -360,9 +415,10 @@ page_flip(struct sna *sna,
if (event == NULL)
return FALSE;
- event->event_id = event_id;
event->crtc = crtc ? crtc->devPrivate : NULL;
event->sna = sna;
+ event->event_id = event_id;
+ event->target_msc = 0;
if (!sna_page_flip(sna, bo, present_flip_handler, event)) {
DBG(("%s: pageflip failed\n", __FUNCTION__));
diff --git a/test/present-test.c b/test/present-test.c
index dc399c39..e90ef477 100644
--- a/test/present-test.c
+++ b/test/present-test.c
@@ -44,6 +44,7 @@
#endif
#include <xcb/xcb.h>
#include <xcb/present.h>
+#include <xcb/xfixes.h>
#include <xf86drm.h>
#include <i915_drm.h>
@@ -170,6 +171,7 @@ static void teardown_msc(Display *dpy, void *q)
}
static int test_whole(Display *dpy)
{
+ xcb_connection_t *c = XGetXCBConnection(dpy);
Pixmap pixmap;
struct dri3_fence fence;
Window root;
@@ -189,8 +191,7 @@ static int test_whole(Display *dpy)
xshmfence_reset(fence.addr);
pixmap = XCreatePixmap(dpy, root, width, height, depth);
- xcb_present_pixmap(XGetXCBConnection(dpy),
- root, pixmap,
+ xcb_present_pixmap(c, root, pixmap,
0, /* sbc */
0, /* valid */
0, /* update */
@@ -207,8 +208,7 @@ static int test_whole(Display *dpy)
XFreePixmap(dpy, pixmap);
pixmap = XCreatePixmap(dpy, root, width, height, depth);
- xcb_present_pixmap(XGetXCBConnection(dpy),
- root, pixmap,
+ xcb_present_pixmap(c, root, pixmap,
0, /* sbc */
0, /* valid */
0, /* update */
@@ -234,6 +234,118 @@ static int test_whole(Display *dpy)
return ret;
}
+static int test_future(Display *dpy, void *Q)
+{
+#define N_VBLANKS 256 /* kernel event queue length: 128 vblanks */
+ xcb_connection_t *c = XGetXCBConnection(dpy);
+ Pixmap pixmap;
+ struct dri3_fence fence;
+ Window root;
+ xcb_xfixes_region_t region;
+ unsigned int width, height;
+ unsigned border, depth;
+ int x, y, ret = 1, n;
+ uint64_t target, final;
+
+ XGetGeometry(dpy, DefaultRootWindow(dpy),
+ &root, &x, &y, &width, &height, &border, &depth);
+
+ if (dri3_create_fence(dpy, root, &fence))
+ return 0;
+
+ printf("Testing whole screen flips into the future: %dx%d\n", width, height);
+ _x_error_occurred = 0;
+
+ region = xcb_generate_id(c);
+ xcb_xfixes_create_region(c, region, 0, NULL);
+
+ target = check_msc(dpy, root, Q, 0);
+ pixmap = XCreatePixmap(dpy, root, width, height, depth);
+ xshmfence_reset(fence.addr);
+ for (n = N_VBLANKS; n--; )
+ xcb_present_pixmap(c, root, pixmap,
+ n, /* sbc */
+ 0, /* valid */
+ region, /* update */
+ 0, /* x_off */
+ 0, /* y_off */
+ None,
+ None, /* wait fence */
+ None,
+ XCB_PRESENT_OPTION_NONE,
+ target + N_VBLANKS, /* target msc */
+ 1, /* divisor */
+ 0, /* remainder */
+ 0, NULL);
+ xcb_present_pixmap(c, root, pixmap,
+ N_VBLANKS, /* sbc */
+ region, /* valid */
+ region, /* update */
+ 0, /* x_off */
+ 0, /* y_off */
+ None,
+ None, /* wait fence */
+ fence.xid,
+ XCB_PRESENT_OPTION_NONE,
+ target, /* target msc */
+ 0, /* divisor */
+ 0, /* remainder */
+ 0, NULL);
+ xcb_flush(c);
+
+ XSync(dpy, True);
+ ret = !!xshmfence_await(fence.addr);
+
+ final = check_msc(dpy, root, Q, 0);
+ if (final < target) {
+ printf("First flip too early, MSC was %llu, expected %llu\n",
+ (long long)final, (long long)target);
+ ret++;
+ } else if (final > target + 1) {
+ printf("First flip too late, MSC was %llu, expected %llu\n",
+ (long long)final, (long long)target);
+ ret++;
+ }
+
+ xshmfence_reset(fence.addr);
+ xcb_present_pixmap(c, root, pixmap,
+ N_VBLANKS, /* sbc */
+ region, /* valid */
+ region, /* update */
+ 0, /* x_off */
+ 0, /* y_off */
+ None,
+ None, /* wait fence */
+ fence.xid,
+ XCB_PRESENT_OPTION_NONE,
+ target + N_VBLANKS, /* target msc */
+ 0, /* divisor */
+ 0, /* remainder */
+ 0, NULL);
+ xcb_flush(c);
+ ret = !!xshmfence_await(fence.addr);
+
+ final = check_msc(dpy, root, Q, 0);
+ if (final < target + N_VBLANKS) {
+ printf("Last flip too early, MSC was %llu, expected %llu\n",
+ (long long)final, (long long)(target + N_VBLANKS));
+ ret++;
+ } else if (final > target + N_VBLANKS + 1) {
+ printf("Last flip too late, MSC was %llu, expected %llu\n",
+ (long long)final, (long long)(target + N_VBLANKS));
+ ret++;
+ }
+
+ XFreePixmap(dpy, pixmap);
+ xcb_xfixes_destroy_region(c, region);
+ dri3_fence_free(dpy, &fence);
+
+ XSync(dpy, True);
+ ret += !!_x_error_occurred;
+
+ return ret;
+}
+
static inline XRRScreenResources *_XRRGetScreenResourcesCurrent(Display *dpy, Window window)
{
XRRScreenResources *res;
@@ -672,8 +784,18 @@ fail:
static int has_present(Display *dpy)
{
xcb_connection_t *c = XGetXCBConnection(dpy);
- xcb_present_query_version_reply_t *reply;
xcb_generic_error_t *error = NULL;
+ void *reply;
+
+ reply = xcb_xfixes_query_version_reply(c,
+ xcb_xfixes_query_version(c,
+ XCB_XFIXES_MAJOR_VERSION,
+ XCB_XFIXES_MINOR_VERSION),
+ &error);
+ free(reply);
+ free(error);
+ if (reply == NULL)
+ return 0;
reply = xcb_present_query_version_reply(c,
xcb_present_query_version(c,
@@ -683,8 +805,10 @@ static int has_present(Display *dpy)
free(reply);
free(error);
+ if (reply == NULL)
+ return 0;
- return reply != NULL;
+ return 1;
}
int main(void)
@@ -713,6 +837,9 @@ int main(void)
error += test_whole(dpy);
last_msc = check_msc(dpy, root, queue, last_msc);
+ error += test_future(dpy, queue);
+ last_msc = check_msc(dpy, root, queue, last_msc);
+
error += test_crtc(dpy, queue, last_msc);
last_msc = check_msc(dpy, root, queue, last_msc);