Commit ea038693 authored by Daniel P. Berrange's avatar Daniel P. Berrange

Fix bounds checking for RRE, hextile & copyrect encodings

While the client would bounds check the overall update
region, it failed to bounds check the payload data
parameters.

Add a test case to validate bounds checking.

https://bugzilla.gnome.org/show_bug.cgi?id=778048

CVE-2017-5884
Signed-off-by: default avatarDaniel P. Berrange <berrange@redhat.com>
parent 7f4f2fe8
......@@ -109,7 +109,7 @@ sc_copyright_format:
prev_version_file = /dev/null
exclude_file_name_regexp--sc_bindtextdomain = ^examples/
exclude_file_name_regexp--sc_bindtextdomain = ^examples/|src/.*test.c
exclude_file_name_regexp--sc_preprocessor_indentation = ^*/*.[ch]
......
EXTRA_DIST = libgvnc_sym.version libgvncpulse_sym.version libgtk-vnc_sym.version vncmarshal.txt
TESTS = vncconnectiontest
noinst_PROGRAMS = $(TESTS)
lib_LTLIBRARIES = libgvnc-1.0.la
BUILT_SOURCES =
......@@ -312,6 +316,10 @@ BUILT_SOURCES += $(MARSHAL_FILES) $(ENUM_FILES)
CLEANFILES = $(MARSHAL_FILES) $(ENUM_FILES)
vncconnectiontest_SOURCES = vncconnectiontest.c
vncconnectiontest_CFLAGS = $(GOBJECT_CFLAGS)
vncconnectiontest_LDADD = libgvnc-1.0.la
if WITH_PYTHON
pyexec_LTLIBRARIES = gtkvnc.la
......
......@@ -2168,6 +2168,21 @@ static vnc_connection_tight_sum_pixel_func *vnc_connection_tight_sum_pixel_table
(vnc_connection_tight_sum_pixel_func *)vnc_connection_tight_sum_pixel_32x32,
};
static gboolean vnc_connection_validate_boundary(VncConnection *conn,
guint16 x, guint16 y,
guint16 width, guint16 height)
{
VncConnectionPrivate *priv = conn->priv;
if ((x + width) > priv->width || (y + height) > priv->height) {
vnc_connection_set_error(conn, "Framebuffer update %dx%d at %d,%d "
"outside boundary %dx%d",
width, height, x, y, priv->width, priv->height);
}
return !vnc_connection_has_error(conn);
}
static void vnc_connection_raw_update(VncConnection *conn,
guint16 x, guint16 y,
......@@ -2215,6 +2230,9 @@ static void vnc_connection_copyrect_update(VncConnection *conn,
src_x = vnc_connection_read_u16(conn);
src_y = vnc_connection_read_u16(conn);
if (!vnc_connection_validate_boundary(conn, src_x, src_y, width, height))
return;
vnc_framebuffer_copyrect(priv->fb,
src_x, src_y,
dst_x, dst_y,
......@@ -2257,6 +2275,10 @@ static void vnc_connection_hextile_rect(VncConnection *conn,
xy = vnc_connection_read_u8(conn);
wh = vnc_connection_read_u8(conn);
if (!vnc_connection_validate_boundary(conn, x + nibhi(xy), y + niblo(xy),
nibhi(wh) + 1, niblo(wh) + 1))
return;
vnc_framebuffer_fill(priv->fb, fg,
x + nibhi(xy), y + niblo(xy),
nibhi(wh) + 1, niblo(wh) + 1);
......@@ -2313,6 +2335,9 @@ static void vnc_connection_rre_update(VncConnection *conn,
sub_w = vnc_connection_read_u16(conn);
sub_h = vnc_connection_read_u16(conn);
if (!vnc_connection_validate_boundary(conn, x + sub_x, y + sub_y, sub_w, sub_h))
break;
vnc_framebuffer_fill(priv->fb, fg,
x + sub_x, y + sub_y, sub_w, sub_h);
}
......@@ -3079,22 +3104,6 @@ static void vnc_connection_ext_key_event(VncConnection *conn)
}
static gboolean vnc_connection_validate_boundary(VncConnection *conn,
guint16 x, guint16 y,
guint16 width, guint16 height)
{
VncConnectionPrivate *priv = conn->priv;
if ((x + width) > priv->width || (y + height) > priv->height) {
vnc_connection_set_error(conn, "Framebuffer update %dx%d at %d,%d "
"outside boundary %dx%d",
width, height, x, y, priv->width, priv->height);
}
return !vnc_connection_has_error(conn);
}
static gboolean vnc_connection_framebuffer_update(VncConnection *conn, gint32 etype,
guint16 x, guint16 y,
guint16 width, guint16 height)
......
/*
* GTK VNC Widget
*
* Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws>
* Copyright (C) 2009-2010 Daniel P. Berrange <dan@berrange.com>
*
* This library 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.0 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <config.h>
#include <string.h>
#include <stdlib.h>
#include "vncconnection.h"
#include "vncbaseframebuffer.h"
static gboolean debug;
#if GLIB_CHECK_VERSION(2, 22, 0)
struct GVncTest {
GMutex lock;
GMutex clock;
GCond cond;
int port;
VncConnection *conn;
GMainLoop *loop;
gboolean connected;
gboolean quit;
char *error;
char *pixels;
void (*test_func)(GInputStream *, GOutputStream *);
};
static void test_send_bytes(GOutputStream *os, const guint8 *str, gsize len)
{
g_assert(g_output_stream_write_all(os, str, len, NULL, NULL, NULL));
}
static void test_send_u8(GOutputStream *os, guint8 v)
{
g_assert(g_output_stream_write_all(os, &v, 1, NULL, NULL, NULL));
}
static void test_send_u16(GOutputStream *os, guint16 v)
{
v = GUINT16_TO_BE(v);
g_assert(g_output_stream_write_all(os, &v, 2, NULL, NULL, NULL));
}
static void test_send_u32(GOutputStream *os, guint32 v)
{
v = GUINT32_TO_BE(v);
g_assert(g_output_stream_write_all(os, &v, 4, NULL, NULL, NULL));
}
static void test_send_s32(GOutputStream *os, gint32 v)
{
v = GINT32_TO_BE(v);
g_assert(g_output_stream_write_all(os, &v, 4, NULL, NULL, NULL));
}
static void test_recv_bytes(GInputStream *is, guint8 *str, gsize len)
{
g_assert(g_input_stream_read_all(is, str, len, NULL, NULL, NULL));
}
static void test_recv_u8(GInputStream *is, guint8 v)
{
guint8 e;
g_assert(g_input_stream_read_all(is, &e, 1, NULL, NULL, NULL));
g_assert(e == v);
}
static gpointer test_helper_server(gpointer opaque)
{
struct GVncTest *data = opaque;
GSocketListener *server;
GSocketConnection *client;
GIOStream *ios;
GInputStream *is;
GOutputStream *os;
server = g_socket_listener_new();
data->port = g_socket_listener_add_any_inet_port(server, NULL, NULL);
g_mutex_unlock(&data->lock);
client = g_socket_listener_accept(server, NULL, NULL, NULL);
ios = G_IO_STREAM(client);
is = g_io_stream_get_input_stream(ios);
os = g_io_stream_get_output_stream(ios);
guint8 greeting[] = {
'R', 'F', 'B', ' ',
'0', '0', '3', '.',
'0', '0', '8', '\n',
};
/* Greeting */
test_send_bytes(os, greeting, G_N_ELEMENTS(greeting));
test_recv_bytes(is, greeting, G_N_ELEMENTS(greeting));
/* N auth */
test_send_u8(os, 1);
/* auth == none */
test_send_u8(os, 1);
test_recv_u8(is, 1);
/* auth result */
test_send_u32(os, 0);
data->test_func(is, os);
g_mutex_lock(&data->clock);
while (!data->quit) {
g_cond_wait(&data->cond, &data->clock);
}
g_object_unref(client);
}
static void test_helper_desktop_resize(VncConnection *conn,
int width, int height,
gpointer opaque)
{
struct GVncTest *test = opaque;
const VncPixelFormat *remoteFormat;
VncPixelFormat localFormat = {
.bits_per_pixel = 32,
.depth = 32,
.byte_order = G_BYTE_ORDER,
.true_color_flag = TRUE,
.red_max = 255,
.green_max = 255,
.blue_max = 255,
.red_shift = 0,
.green_shift = 8,
.blue_shift = 16,
};
VncBaseFramebuffer *fb;
VNC_DEBUG("Resize %dx%d", width, height);
remoteFormat = vnc_connection_get_pixel_format(conn);
/* We'll fix our local copy as rgb888 */
test->pixels = g_new0(char, width * height * 4);
fb = vnc_base_framebuffer_new(test->pixels, width, height, width * 4,
remoteFormat,
&localFormat);
vnc_connection_set_framebuffer(conn, VNC_FRAMEBUFFER(fb));
g_object_unref(fb);
}
static void test_helper_initialized(VncConnection *conn,
gpointer opaque)
{
struct GVncTest *test = opaque;
gint32 encodings[] = { VNC_CONNECTION_ENCODING_DESKTOP_RESIZE,
VNC_CONNECTION_ENCODING_ZRLE,
VNC_CONNECTION_ENCODING_HEXTILE,
VNC_CONNECTION_ENCODING_RRE,
VNC_CONNECTION_ENCODING_COPY_RECT,
VNC_CONNECTION_ENCODING_RAW };
gint32 *encodingsp;
int n_encodings;
test_helper_desktop_resize(conn,
vnc_connection_get_width(conn),
vnc_connection_get_height(conn),
test);
encodingsp = encodings;
n_encodings = G_N_ELEMENTS(encodings);
VNC_DEBUG("Sending %d encodings", n_encodings);
if (!vnc_connection_set_encodings(conn, n_encodings, encodingsp))
goto error;
VNC_DEBUG("Requesting first framebuffer update");
if (!vnc_connection_framebuffer_update_request(test->conn,
0, 0, 0,
vnc_connection_get_width(test->conn),
vnc_connection_get_height(test->conn)))
vnc_connection_shutdown(test->conn);
test->connected = TRUE;
return;
error:
vnc_connection_shutdown(conn);
}
static void test_helper_auth_choose_type(VncConnection *conn,
GValueArray *types G_GNUC_UNUSED,
gpointer opaque G_GNUC_UNUSED)
{
vnc_connection_set_auth_type(conn, VNC_CONNECTION_AUTH_NONE);
}
static void test_helper_disconnected(VncConnection *conn G_GNUC_UNUSED,
gpointer opaque)
{
struct GVncTest *test = opaque;
g_main_quit(test->loop);
}
static void test_helper_error(VncConnection *conn,
const char *str,
gpointer opaque)
{
struct GVncTest *test = opaque;
test->error = g_strdup(str);
}
static void test_common_bounds_server(GInputStream *is, GOutputStream *os)
{
/* Frame buffer width / height */
test_send_u16(os, 100);
test_send_u16(os, 100);
/* BPP, depth, endian, true color */
test_send_u8(os, 32);
test_send_u8(os, 8);
test_send_u8(os, 1);
test_send_u8(os, 1);
/* RGB max + shift*/
test_send_u16(os, 255);
test_send_u16(os, 255);
test_send_u16(os, 255);
test_send_u8(os, 0);
test_send_u8(os, 8);
test_send_u8(os, 16);
guint8 pad[3] = {0};
test_send_bytes(os, pad, G_N_ELEMENTS(pad));
/* name */
guint8 name[] = { 'T', 'e', 's', 't' };
test_send_u32(os, G_N_ELEMENTS(name));
test_send_bytes(os, name, G_N_ELEMENTS(name));
}
static void test_rre_bounds_server(GInputStream *is, GOutputStream *os)
{
test_common_bounds_server(is, os);
/* Message type & pad */
test_send_u8(os, 0);
test_send_u8(os, 0);
/* num rect */
test_send_u16(os, 1);
/* x, y, w, h */
test_send_u16(os, 90);
test_send_u16(os, 90);
test_send_u16(os, 10);
test_send_u16(os, 10);
/* encoding=rre */
test_send_s32(os, 2);
/* num rect */
test_send_u32(os, 1);
/* bg pix, fg pix */
test_send_u32(os, 0x41414141);
test_send_u32(os, 0x42424242);
/* x, y, w, h */
test_send_u16(os, 10);
test_send_u16(os, 10000);
test_send_u16(os, 1);
test_send_u16(os, 1);
}
static void test_hextile_bounds_server(GInputStream *is, GOutputStream *os)
{
test_common_bounds_server(is, os);
/* Message type & pad */
test_send_u8(os, 0);
test_send_u8(os, 0);
/* num rect */
test_send_u16(os, 1);
/* x, y, w, h */
test_send_u16(os, 90);
test_send_u16(os, 90);
test_send_u16(os, 10);
test_send_u16(os, 10);
/* encoding=hextile */
test_send_s32(os, 5);
/* tile type */
test_send_u8(os, 0x18);
/* num rect */
test_send_u8(os, 1);
/* fg pix */
test_send_u32(os, 0x12345678);
/* x, y */
test_send_u8(os, 0xff);
test_send_u8(os, 0xff);
}
static void test_copyrect_bounds_server(GInputStream *is, GOutputStream *os)
{
test_common_bounds_server(is, os);
/* Message type & pad */
test_send_u8(os, 0);
test_send_u8(os, 0);
/* num rect */
test_send_u16(os, 1);
/* x, y, w, h */
test_send_u16(os, 90);
test_send_u16(os, 90);
test_send_u16(os, 10);
test_send_u16(os, 10);
/* encoding=copyrect */
test_send_s32(os, 1);
/* src x, y */
test_send_u16(os, 91);
test_send_u16(os, 91);
}
static void test_validation(void (*test_func)(GInputStream *, GOutputStream *))
{
struct GVncTest *test;
char *port;
GMainLoop *loop;
GThread *th;
test = g_new0(struct GVncTest, 1);
test->test_func = test_func;
g_mutex_init(&test->lock);
g_mutex_init(&test->clock);
g_cond_init(&test->cond);
g_mutex_lock(&test->lock);
loop = g_main_loop_new(g_main_context_default(), FALSE);
th = g_thread_new("rre-server", test_helper_server, test);
g_mutex_lock(&test->lock);
port = g_strdup_printf("%d", test->port);
test->conn = vnc_connection_new();
g_signal_connect(test->conn, "vnc-initialized",
G_CALLBACK(test_helper_initialized), test);
g_signal_connect(test->conn, "vnc-disconnected",
G_CALLBACK(test_helper_disconnected), test);
g_signal_connect(test->conn, "vnc-auth-choose-type",
G_CALLBACK(test_helper_auth_choose_type), test);
g_signal_connect(test->conn, "vnc-desktop-resize",
G_CALLBACK(test_helper_desktop_resize), test);
g_signal_connect(test->conn, "vnc-error",
G_CALLBACK(test_helper_error), test);
vnc_connection_open_host(test->conn, "127.0.0.1", port);
test->loop = g_main_loop_new(g_main_context_default(), FALSE);
g_main_loop_run(test->loop);
g_mutex_lock(&test->clock);
test->quit = TRUE;
g_mutex_unlock(&test->clock);
g_cond_signal(&test->cond);
g_thread_join(th);
vnc_connection_shutdown(test->conn);
g_object_unref(test->conn);
g_free(test->pixels);
g_main_loop_unref(test->loop);
g_assert(test->error);
if (debug)
g_printerr("Got err %s\n", test->error);
g_free(test->error);
g_free(port);
g_free(test);
}
static void test_validation_rre(void)
{
test_validation(test_rre_bounds_server);
}
static void test_validation_hextile(void)
{
test_validation(test_hextile_bounds_server);
}
static void test_validation_copyrect(void)
{
test_validation(test_copyrect_bounds_server);
}
#endif
int main(int argc, char **argv) {
g_test_init(&argc, &argv, NULL);
if (getenv("GTK_VNC_DEBUG")) {
debug = TRUE;
vnc_util_set_debug(TRUE);
}
#if GLIB_CHECK_VERSION(2, 22, 0)
g_test_add_func("/conn/validation/rre", test_validation_rre);
g_test_add_func("/conn/validation/copyrect", test_validation_copyrect);
g_test_add_func("/conn/validation/hextile", test_validation_hextile);
#endif
return g_test_run();
}
/*
* Local variables:
* c-indent-level: 4
* c-basic-offset: 4
* indent-tabs-mode: nil
* End:
*/
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment