diff --git a/docs/graphics-protocol.rst b/docs/graphics-protocol.rst index c977a569f..be45e6e73 100644 --- a/docs/graphics-protocol.rst +++ b/docs/graphics-protocol.rst @@ -201,7 +201,11 @@ Value of `t` Meaning ================== ============ ``d`` Direct (the data is transmitted within the escape code itself) ``f`` A simple file -``t`` A temporary file, the terminal emulator will delete the file after reading the pixel data +``t`` A temporary file, the terminal emulator will delete the file after reading the pixel data. For security reasons + the terminal emulator should only delete the file if it + is in a known temporary directory, such as :file:`/tmp`, + :file:`/dev/shm`, :file:`TMPDIR env var if present` and any platform + specific temporary directories. ``s`` A `POSIX shared memory object `_. The terminal emulator will delete it after reading the pixel data ================== ============ diff --git a/kitty/boss.py b/kitty/boss.py index 311deef7d..31dc70c59 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -32,9 +32,9 @@ from .rgb import Color, color_from_int from .session import create_session from .tabs import SpecialWindow, SpecialWindowInstance, TabManager from .utils import ( - get_editor, get_primary_selection, log_error, open_url, parse_address_spec, - remove_socket_file, safe_print, set_primary_selection, single_instance, - startup_notification_handler + get_editor, get_primary_selection, is_path_in_temp_dir, log_error, + open_url, parse_address_spec, remove_socket_file, safe_print, + set_primary_selection, single_instance, startup_notification_handler ) @@ -959,3 +959,7 @@ class Boss: for tm in self.all_tab_managers: tm.tab_bar.patch_colors(spec) patch_global_colors(spec, configured) + + def safe_delete_temp_file(self, path): + if is_path_in_temp_dir(path): + os.remove(path) diff --git a/kitty/graphics.c b/kitty/graphics.c index ffc97b475..098e1b472 100644 --- a/kitty/graphics.c +++ b/kitty/graphics.c @@ -368,7 +368,7 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_ if (fd == -1) ABRT(EBADF, "Failed to open file %s for graphics transmission with error: [%d] %s", fname, errno, strerror(errno)); img->data_loaded = mmap_img_file(self, img, fd, g->data_sz, g->data_offset); close(fd); - if (tt == 't') unlink(fname); + if (tt == 't') { call_boss(safe_delete_temp_file, "s", fname); } else if (tt == 's') shm_unlink(fname); break; default: diff --git a/kitty/utils.py b/kitty/utils.py index c9ed2ab56..aa17d202c 100644 --- a/kitty/utils.py +++ b/kitty/utils.py @@ -432,3 +432,20 @@ def get_editor(): ans = shlex.split(ans) get_editor.ans = ans return ans + + +def is_path_in_temp_dir(path): + if not path: + return False + + def abspath(x): + if x: + return os.path.abspath(os.path.realpath(x)) + + import tempfile + path = abspath(path) + candidates = frozenset(map(abspath, ('/tmp', '/dev/shm', os.environ.get('TMPDIR', None), tempfile.gettempdir()))) + for q in candidates: + if q and path.startswith(q): + return True + return False diff --git a/kitty_tests/datatypes.py b/kitty_tests/datatypes.py index a474869f0..a506a8dca 100644 --- a/kitty_tests/datatypes.py +++ b/kitty_tests/datatypes.py @@ -2,13 +2,16 @@ # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2016, Kovid Goyal +import os +import tempfile + from kitty.config import build_ansi_color_table, defaults from kitty.fast_data_types import ( REVERSE, ColorProfile, Cursor as C, HistoryBuf, LineBuf, - parse_input_from_terminal, wcswidth, wcwidth, truncate_point_for_length + parse_input_from_terminal, truncate_point_for_length, wcswidth, wcwidth ) from kitty.rgb import to_color -from kitty.utils import sanitize_title +from kitty.utils import is_path_in_temp_dir, sanitize_title from . import BaseTest, filled_cursor, filled_history_buf, filled_line_buf @@ -384,6 +387,12 @@ class TestDataTypes(BaseTest): tp('a\033', '_', 'x\033', '\\b', text='a b', apc='x') tp('a\033_', 'x', '\033', '\\', 'b', text='a b', apc='x') + for prefix in ('/tmp', tempfile.gettempdir()): + for path in ('a.png', 'x/b.jpg', 'y/../c.jpg'): + self.assertTrue(is_path_in_temp_dir(os.path.join(prefix, path))) + for path in ('/home/xy/d.png', '/tmp/../home/x.jpg'): + self.assertFalse(is_path_in_temp_dir(os.path.join(path))) + def test_color_profile(self): c = ColorProfile() c.update_ansi_color_table(build_ansi_color_table())