Skip to content

Commit

Permalink
#1312: detect clipboard loops using random uuid values
Browse files Browse the repository at this point in the history
git-svn-id: https://xpra.org/svn/Xpra/trunk@18229 3bb7dfac-3a0b-4e04-842a-767bc560f471
  • Loading branch information
totaam committed Jan 31, 2018
1 parent 38eb7c4 commit 8441205
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 14 deletions.
1 change: 1 addition & 0 deletions src/xpra/client/gtk_base/gtk_client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1230,6 +1230,7 @@ def setup_clipboard_helper(self, helperClass):
"clipboards.remote" : self.server_clipboards, #all the remote clipboards supported
"can-send" : self.client_clipboard_direction in ("to-server", "both"),
"can-receive" : self.client_clipboard_direction in ("to-client", "both"),
"remote-loop-uuids" : self.server_clipboard_loop_uuids,
}
#only allow translation overrides if we have a way of telling the server about them:
if self.server_clipboard_enable_selections:
Expand Down
10 changes: 10 additions & 0 deletions src/xpra/client/ui_client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1872,6 +1872,7 @@ def parse_server_capabilities(self):
self.server_bell = c.boolget("bell") #added in 0.5, default to True!
self.bell_enabled = self.server_bell and self.client_supports_bell
self.server_clipboard = c.boolget("clipboard")
self.server_clipboard_loop_uuids = c.dictget("clipboard.loop-uuids")
self.server_clipboard_direction = c.strget("clipboard-direction", "both")
if self.server_clipboard_direction!=self.client_clipboard_direction and self.server_clipboard_direction!="both":
if self.client_clipboard_direction=="disabled":
Expand Down Expand Up @@ -1992,6 +1993,8 @@ def process_ui_capabilities(self):
#(could have been translated, or limited if the client only has one, etc)
clipboardlog("clipboard enabled clipboard helper=%s", self.clipboard_helper)
self.send_clipboard_selections(self.clipboard_helper.remote_clipboards)
if self.clipboard_enabled and self.server_clipboard_loop_uuids:
self.send_clipboard_loop_uuids()
self.set_max_packet_size()
self.send_deflate_level()
c = self.server_capabilities
Expand Down Expand Up @@ -2982,6 +2985,13 @@ def send_clipboard_selections(self, selections):
if self.server_clipboard_enable_selections:
self.send("clipboard-enable-selections", selections)

def send_clipboard_loop_uuids(self):
uuids = self.clipboard_helper.get_loop_uuids()
clipboardlog("send_clipboard_loop_uuid() uuids=%s", uuids)
if self.server_clipboard_loop_uuids:
self.send("clipboard-loop-uuids", uuids)


def send_keyboard_sync_enabled_status(self, *_args):
self.send("set-keyboard-sync-enabled", self.keyboard_sync)

Expand Down
86 changes: 72 additions & 14 deletions src/xpra/clipboard/clipboard_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
from xpra.gtk_common.gtk_util import GetClipboard, selection_owner_set, selection_add_target, selectiondata_get_selection, selectiondata_get_target, selectiondata_get_data, selectiondata_get_data_type, selectiondata_get_format, selectiondata_set, clipboard_request_contents, PROPERTY_CHANGE_MASK
from xpra.gtk_common.nested_main import NestedMainLoop
from xpra.net.compression import Compressible
from xpra.os_util import WIN32, POSIX, monotonic_time, strtobytes, bytestostr, hexstr
from xpra.util import csv, envint, envbool, repr_ellipsized
from xpra.os_util import WIN32, POSIX, monotonic_time, strtobytes, bytestostr, hexstr, get_hex_uuid
from xpra.util import csv, envint, envbool, repr_ellipsized, nonl, typedict
from xpra.platform.features import CLIPBOARD_GREEDY


Expand All @@ -42,6 +42,9 @@
STORE_ON_EXIT = envbool("XPRA_CLIPBOARD_STORE_ON_EXIT", True)
DELAY_SEND_TOKEN = envint("XPRA_DELAY_SEND_TOKEN", 100)

LOOP_DISABLE = envbool("XPRA_CLIPBOARD_LOOP_DISABLE", True)
LOOP_PREFIX = os.environ.get("XPRA_CLIPBOARD_LOOP_PREFIX", "Xpra-Clipboard-Loop-Detection:")

_discard_target_strs_ = os.environ.get("XPRA_DISCARD_TARGETS")
if _discard_target_strs_ is not None:
DISCARD_TARGETS = _discard_target_strs_.split(",")
Expand Down Expand Up @@ -98,25 +101,30 @@ def _filter_targets(targets):

class ClipboardProtocolHelperBase(object):
def __init__(self, send_packet_cb, progress_cb=None, **kwargs):
d = typedict(kwargs)
self.send = send_packet_cb
self.progress_cb = progress_cb
self.can_send = kwargs.get("can-send", True)
self.can_receive = kwargs.get("can-receive", True)
self.can_send = d.boolget("can-send", True)
self.can_receive = d.boolget("can-receive", True)
self.max_clipboard_packet_size = MAX_CLIPBOARD_PACKET_SIZE
self.filter_res = []
filter_res = kwargs.get("filters")
filter_res = d.strlistget("filters")
if filter_res:
for x in filter_res:
try:
self.filter_res.append(re.compile(x))
except:
log.error("invalid regular expression '%s' in clipboard filter")
except Exception as e:
log.error("Error: invalid clipboard filter regular expression")
log.error(" '%s': %s", x, e)
self._clipboard_request_counter = 0
self._clipboard_outstanding_requests = {}
self._want_targets = False
self.init_packet_handlers()
self.init_proxies(kwargs.get("clipboards.local", CLIPBOARDS))
self.remote_clipboards = kwargs.get("clipboards.remote", CLIPBOARDS)
self.init_proxies(d.strlistget("clipboards.local", CLIPBOARDS))
remote_loop_uuids = d.dictget("remote-loop-uuids", {})
self.verify_remote_loop_uuids(remote_loop_uuids)
self.remote_clipboards = d.strlistget("clipboards.remote", CLIPBOARDS)
self.init_proxies_uuid()

def __repr__(self):
return "ClipboardProtocolHelperBase"
Expand Down Expand Up @@ -145,6 +153,34 @@ def nosend(*args):
x.cleanup()
self._clipboard_proxies = {}


def get_loop_uuids(self):
uuids = {}
for proxy in self._clipboard_proxies.values():
uuids[proxy._selection] = proxy._loop_uuid
log("get_loop_uuids()=%s", uuids)
return uuids

def verify_remote_loop_uuids(self, uuids):
log("verify_remote_loop_uuids(%s)", uuids)
for proxy in self._clipboard_proxies.values():
proxy._clipboard.request_text(self._verify_remote_loop_uuids, (proxy, uuids))

def _verify_remote_loop_uuids(self, clipboard, value, user_data):
log("_verify_remote_loop_uuids(%s)", (clipboard, value, user_data))
proxy, uuids = user_data
if value:
for selection, rvalue in uuids.items():
if rvalue and value==rvalue:
if selection==proxy._selection:
log.warn("Warning: loop detected for %s clipboard", selection)
else:
log.warn("Warning: loop detected")
log.warn(" local %s clipboard matches remote %s clipboard", proxy._selection, selection)
if LOOP_DISABLE:
log.warn(" synchronization has been disabled")
proxy._enabled = False

def set_direction(self, can_send, can_receive):
self.can_send = can_send
self.can_receive = can_receive
Expand Down Expand Up @@ -175,6 +211,7 @@ def init_packet_handlers(self):
b"clipboard-contents-none" : self._process_clipboard_contents_none,
b"clipboard-pending-requests" : self._process_clipboard_pending_requests,
b"clipboard-enable-selections" : self._process_clipboard_enable_selections,
b"clipboard-loop-uuids" : self._process_clipboard_loop_uuids,
}

def make_proxy(self, clipboard):
Expand All @@ -191,6 +228,11 @@ def init_proxies(self, clipboards):
self._clipboard_proxies[clipboard] = proxy
log("%s.init_proxies : %s", self, self._clipboard_proxies)

def init_proxies_uuid(self):
for proxy in self._clipboard_proxies.values():
proxy.init_uuid()


def local_to_remote(self, selection):
#overriden in some subclasses (see: translated_clipboard)
return selection
Expand Down Expand Up @@ -451,6 +493,10 @@ def _process_clipboard_enable_selections(self, packet):
selections = packet[1]
self.enable_selections(selections)

def _process_clipboard_loop_uuids(self, packet):
loop_uuids = packet[1]
self.verify_remote_loop_uuids(loop_uuids)


def process_clipboard_packet(self, packet):
packet_type = packet[0]
Expand Down Expand Up @@ -509,8 +555,14 @@ def __init__(self, selection):
except ImportError:
self.prop_get = None

self._loop_uuid = ""
self._clipboard.connect("owner-change", self.do_owner_changed)

def init_uuid(self):
self._loop_uuid = LOOP_PREFIX+get_hex_uuid()
log("init_uuid() %s set_text(%s)", self._selection, self._loop_uuid)
self._clipboard.set_text(self._loop_uuid)

def set_direction(self, can_send, can_receive):
self._can_send = can_send
self._can_receive = can_receive
Expand All @@ -522,6 +574,7 @@ def get_info(self):
"greedy_client" : self._greedy_client,
"blocked_owner_change" : self._block_owner_change,
"last-targets" : self._last_targets,
"loop-uuid" : self._loop_uuid,
"event" : {
"selection_request" : self._selection_request_events,
"selection_get" : self._selection_get_events,
Expand Down Expand Up @@ -801,11 +854,16 @@ def unpack(clipboard, selection_data, _user_data=None):
dtype = selectiondata_get_data_type(selection_data)
dformat = selectiondata_get_format(selection_data)
log("unpack(..) type=%s, format=%s, data=%s:%s", dtype, dformat, type(data), len(data or ""))
if self._strip_nullbyte and dtype in (b"UTF8_STRING", b"STRING") and dformat==8:
#we may have to strip the nullbyte:
if data and data[-1]=='\0':
log("stripping end of string null byte")
data = data[:-1]
isstring = dtype in (b"UTF8_STRING", b"STRING") and dformat==8
if isstring:
if self._strip_nullbyte:
#we may have to strip the nullbyte:
if data and data[-1]=='\0':
log("stripping end of string null byte")
data = data[:-1]
if data and data==self._loop_uuid:
log("not sending loop uuid value '%s', returning an empty string instead", data)
data= ""
cb(str(dtype), dformat, data)
#some applications (ie: firefox, thunderbird) can request invalid targets,
#when that happens, translate it to something the application can handle (if any)
Expand Down
2 changes: 2 additions & 0 deletions src/xpra/server/server_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1714,6 +1714,8 @@ def make_hello(self, source):
"server-commands-signals" : COMMAND_SIGNALS,
"server-commands-info" : not WIN32 and not OSX,
})
if self._clipboard_helper:
capabilities["clipboard.loop-uuids"] = self._clipboard_helper.get_loop_uuids()
capabilities.update(self.file_transfer.get_file_transfer_features())
capabilities.update(flatten_dict(self.get_server_features()))
#this is a feature, but we would need the hello request
Expand Down

0 comments on commit 8441205

Please sign in to comment.