#1012046 /usr/libexec/gnome-terminal-server: gnome-terminal-server writes on disk data when a program output data on term

Package:
gnome-terminal
Source:
gnome-terminal
Description:
GNOME terminal emulator application
Submitter:
Ludovic Pouzenc
Date:
2023-04-28 19:42:03 UTC
Severity:
normal
#1012046#5
Date:
2022-05-29 10:01:59 UTC
From:
To:
Dear Maintainer,

I see on debian 10, 11 and testing a potential security problem with
gnome-terminal-server. It makes IO on disk when some program output on terminal.

It uses deleted files in /tmp instead of no files or files in RAM in /run.

My use case is sysadmin a lot of machines, with sometimes confidential
data displayed on terminal. For me everything should be in RAM as
xterm does.

The simplest way to spot code path that seems to be bad for me is :
   * install debian 10, 11 or testing on a physical amd64 computer
   * open a gnome session with a normal user
   * open a gnome-terminal
   * wait until there is not significant activity on IO physical LED
   * start the following command : yes
   * terminal starts to scroll fast
   * IO LED should go to "solid on" now, because many IO
   * sudo apt install iotop strace
   * sudo iotop should display something like :

Total DISK READ:         0.00 B/s | Total DISK WRITE:         2.21 M/s
Current DISK READ:       0.00 B/s | Current DISK WRITE:     191.00 K/s
    TID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN     IO>    COMMAND
   2260 be/4 lpouzenc    0.00 B/s  176.86 K/s  ?unavailable?  gnome-terminal-server
      1 be/4 root        0.00 B/s    0.00 B/s  ?unavailable?  init
      2 be/4 root        0.00 B/s    0.00 B/s  ?unavailable?  [kthreadd]

   * sudo strace -p2260 -fc # for 10 seconds or so
strace: Process 2260 attached with 4 threads
^Cstrace: Process 2260 detached
strace: Process 2315 detached
strace: Process 2316 detached
strace: Process 2327 detached
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 66,59    0,999115        1546       646           poll
 32,06    0,480939           4    100426         4 read
  0,31    0,004600           2      2156           pread64
  0,26    0,003925           4       915       593 recvmsg
  0,19    0,002921           1      2334           pwrite64
  0,16    0,002460           1      1898           ftruncate
  0,13    0,001926           6       312           write
  0,08    0,001240          10       116           fallocate
  0,07    0,001086           7       136        31 futex
  0,05    0,000774         774         1           restart_syscall
  0,04    0,000645          11        57           sendmsg
  0,04    0,000550           7        75           writev
  0,01    0,000109          13         8           ioctl
  0,00    0,000045           2        20           clock_nanosleep
------ ----------- ----------- --------- --------- ----------------
100,00    1,500335          13    109100       628 total

   * sudo strace -p2260 -fo /dev/shm/gts
   * less /dev/shm/gts
[...]
2260  write(14, "\r", 1)                = 1
2260  recvmsg(3, {msg_namelen=0}, 0)    = -1 EAGAIN (Ressource temporairement non disponible)
2260  poll([{fd=3, events=POLLIN}, {fd=4, events=POLLIN}, {fd=5, events=POLLIN}, {fd=13, events=POLLIN|POLLPRI}, {fd=14, events=POLLIN|POLLPRI}, {fd=19, events=POLLIN|POLLPRI}], 6, 600) = 1 ([{fd=14, revents=POLLIN}])
2260  write(4, "\1\0\0\0\0\0\0\0", 8)   = 8
2260  read(14, "\0\r\n\33[?2004l\r", 8136) = 12
2260  read(14, 0x562c26142083, 8125)    = -1 EAGAIN (Ressource temporairement non disponible)
2260  write(4, "\1\0\0\0\0\0\0\0", 8)   = 8
2260  recvmsg(3, {msg_namelen=0}, 0)    = -1 EAGAIN (Ressource temporairement non disponible)
2260  poll([{fd=3, events=POLLIN}, {fd=4, events=POLLIN}, {fd=5, events=POLLIN}, {fd=13, events=POLLIN|POLLPRI}, {fd=14, events=POLLIN|POLLPRI}, {fd=19, events=POLLIN|POLLPRI}], 6, 10) = 1 ([{fd=4, revents=POLLIN}])
2260  read(4, "\2\0\0\0\0\0\0\0", 16)   = 8
2260  recvmsg(3, {msg_namelen=0}, 0)    = -1 EAGAIN (Ressource temporairement non disponible)
2260  poll([{fd=3, events=POLLIN}, {fd=4, events=POLLIN}, {fd=5, events=POLLIN}, {fd=13, events=POLLIN|POLLPRI}, {fd=14, events=POLLIN|POLLPRI}, {fd=19, events=POLLIN|POLLPRI}], 6, 10) = 1 ([{fd=14, revents=POLLIN}])
2260  write(4, "\1\0\0\0\0\0\0\0", 8)   = 8
2260  read(14, "\0y\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny"..., 8125) = 1361
2260  read(14, "\0\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r"..., 6765) = 289
2260  read(14, "\0\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r"..., 6477) = 295
2260  read(14, "\0\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r"..., 6183) = 292
2260  read(14, "\0\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r"..., 5892) = 292
2260  read(14, "\0\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r"..., 5601) = 288
2260  read(14, "\0y\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny\r\ny"..., 5314) = 262
[...]

   * sudo lsof -np 2260 |  grep -v mem
lsof: WARNING: can't stat() fuse.gvfsd-fuse file system /run/user/1000/gvfs
      Output information may be incomplete.
lsof: WARNING: can't stat() fuse.portal file system /run/user/1000/doc
      Output information may be incomplete.
COMMAND    PID     USER   FD      TYPE             DEVICE SIZE/OFF    NODE NAME
gnome-ter 2260 lpouzenc  cwd       DIR               8,20     4096 3014658 /home/lpouzenc
gnome-ter 2260 lpouzenc  rtd       DIR               8,20     4096       2 /
gnome-ter 2260 lpouzenc  txt       REG               8,20   395968 1105162 /usr/libexec/gnome-terminal-server
gnome-ter 2260 lpouzenc  DEL       REG                0,1               13 /SYSV00000000
gnome-ter 2260 lpouzenc    0r      CHR                1,3      0t0       4 /dev/null
gnome-ter 2260 lpouzenc    1u     unix 0x000000003b37b6a7      0t0   25426 type=STREAM (CONNECTED)
gnome-ter 2260 lpouzenc    2u     unix 0x000000003b37b6a7      0t0   25426 type=STREAM (CONNECTED)
gnome-ter 2260 lpouzenc    3u     unix 0x0000000079cc544f      0t0   28206 type=STREAM (CONNECTED)
gnome-ter 2260 lpouzenc    4u  a_inode               0,14        0    8545 [eventfd:76]
gnome-ter 2260 lpouzenc    5u     unix 0x00000000b44afd39      0t0   25450 type=STREAM (CONNECTED)
gnome-ter 2260 lpouzenc    6u  a_inode               0,14        0    8545 [eventfd:77]
gnome-ter 2260 lpouzenc    7u  a_inode               0,14        0    8545 [eventfd:78]
gnome-ter 2260 lpouzenc    8u     unix 0x00000000c4fab42c      0t0   27266 type=STREAM (CONNECTED)
gnome-ter 2260 lpouzenc    9u  a_inode               0,14        0    8545 [eventfd:79]
gnome-ter 2260 lpouzenc   10u  a_inode               0,14        0    8545 [eventfd:82]
gnome-ter 2260 lpouzenc   11r  a_inode               0,14        0    8545 inotify
gnome-ter 2260 lpouzenc   12u     unix 0x000000004ea08462      0t0   52405 type=STREAM (CONNECTED)
gnome-ter 2260 lpouzenc   13u      CHR                5,2      0t0      85 /dev/ptmx
gnome-ter 2260 lpouzenc   14u      CHR                5,2      0t0      85 /dev/ptmx
gnome-ter 2260 lpouzenc   15u      REG               8,20    65536 2753222 /tmp/#2753222 (deleted)
gnome-ter 2260 lpouzenc   16u      REG               8,20   196608 2752545 /tmp/#2752545 (deleted)
gnome-ter 2260 lpouzenc   17u      REG               8,20   196608 2752546 /tmp/#2752546 (deleted)
gnome-ter 2260 lpouzenc   18u      REG               8,20    65536 2753227 /tmp/#2753227 (deleted)
gnome-ter 2260 lpouzenc   19u      CHR                5,2      0t0      85 /dev/ptmx
gnome-ter 2260 lpouzenc   20u      REG               8,20  1507328 2752519 /tmp/#2752519 (deleted)
gnome-ter 2260 lpouzenc   21u      REG               8,20   196608 2752522 /tmp/#2752522 (deleted)

    * grep -vF 'read(14' /dev/shm/gts | less
[...]
2260  write(14, "\r", 1)                = 1
2260  recvmsg(3, {msg_namelen=0}, 0)    = -1 EAGAIN (Ressource temporairement non disponible)
2260  poll([{fd=3, events=POLLIN}, {fd=4, events=POLLIN}, {fd=5, events=POLLIN}, {fd=13, events=POLLIN|POLLPRI}, {fd=14, events=POLLIN|POLLPRI}, {fd=19, events=POLLIN|POLLPRI}], 6, 600) = 1 ([{fd=14, revents=POLLIN}])
2260  write(4, "\1\0\0\0\0\0\0\0", 8)   = 8
2260  write(4, "\1\0\0\0\0\0\0\0", 8)   = 8
2260  recvmsg(3, {msg_namelen=0}, 0)    = -1 EAGAIN (Ressource temporairement non disponible)
2260  poll([{fd=3, events=POLLIN}, {fd=4, events=POLLIN}, {fd=5, events=POLLIN}, {fd=13, events=POLLIN|POLLPRI}, {fd=14, events=POLLIN|POLLPRI}, {fd=19, events=POLLIN|POLLPRI}], 6, 10) = 1 ([{fd=4, revents=POLLIN}])
2260  read(4, "\2\0\0\0\0\0\0\0", 16)   = 8
2260  recvmsg(3, {msg_namelen=0}, 0)    = -1 EAGAIN (Ressource temporairement non disponible)
2260  poll([{fd=3, events=POLLIN}, {fd=4, events=POLLIN}, {fd=5, events=POLLIN}, {fd=13, events=POLLIN|POLLPRI}, {fd=14, events=POLLIN|POLLPRI}, {fd=19, events=POLLIN|POLLPRI}], 6, 10) = 1 ([{fd=14, revents=POLLIN}])
2260  write(4, "\1\0\0\0\0\0\0\0", 8)   = 8
2260  write(4, "\1\0\0\0\0\0\0\0", 8)   = 8
2260  write(4, "\1\0\0\0\0\0\0\0", 8)   = 8
2260  recvmsg(3, {msg_namelen=0}, 0)    = -1 EAGAIN (Ressource temporairement non disponible)
2260  poll([{fd=3, events=POLLIN}, {fd=4, events=POLLIN}, {fd=5, events=POLLIN}], 3, 0) = 1 ([{fd=4, revents=POLLIN}])
2260  read(4, "\3\0\0\0\0\0\0\0", 16)   = 8
2260  write(4, "\1\0\0\0\0\0\0\0", 8)   = 8
2260  pread64(17, "}\24\0\0\1\0\0\0,_\242\332_\r\325\214\277*\3;%Q\310_\3162\344.\222\212\340\247"..., 65536, 262144) = 65536
2260  pwrite64(17, "\305\25\0\0\1\0\0\0\7h\354\266[\236n\323\356\375\225\3076\225\346\243\240\235\240\321<\201\231\37"..., 5597, 0) = 5597
2260  ftruncate(18, 0)                  = 0
2260  pread64(17, "\200\24\0\0\1\0\0\0E%\243\2032x\30%\306\2636S\201\3673\360\213\210\v\263\324y\200."..., 65536, 327680) = 65536
2260  pwrite64(17, "\204\24\0\0\1\0\0\0\363)CS\371}\317ay\367\361\377\367\3V&z\6%vf*\311\222"..., 5276, 65536) = 5276
2260  pread64(17, "\203\24\0\0\1\0\0\09\205\225\336b\2\223\270@\00275\263\26\306t0\234\244\204e\206\266\221"..., 65536, 393216) = 65536
2260  pwrite64(17, "\206\24\0\0\1\0\0\0\304\273\35\267\234\245\255\224\t@w0Rn\324\335\205MGG.\360\262\363"..., 5278, 131072) = 5278
2260  pread64(17, "\305\25\0\0\1\0\0\0\7h\354\266[\236n\323\356\375\225\3076\225\346\243\240\235\240\321<\201\231\37"..., 65536, 0) = 65536
2260  ftruncate(17, 196608)             = 0
[...]


    * echo 'deb http://debug.mirrors.debian.org/debian-debug/ bookworm-debug main' | sudo tee /etc/apt/sources.list.d/dbgsym.list
    * sudo apt install gnome-terminal-dbgsym libvte*-dbgsym ddd
    * cd /tmp
    * apt source gnome-terminal
    * cd gnome-term*/src # there is some ../ is path from dbgsym
    * ddd /usr/libexec/gnome-terminal-server 2260
    * in gdb prompt
        * catch syscall pwrite64
	* cont
    * I have to redo that with libvte sources
    * cd /tmp
    * apt source libvte-2.91-0
    * cd vte2.91-0.68.0/src
    * ddd /usr/libexec/gnome-terminal-server 2260
    * in gdb prompt
        * catch syscall pwrite64
	* cont

../src/vtestream-file.h:258
static void
_file_write (int fd, const char *data, gsize len, gsize offset)
{
        gsize ret;

        if (G_UNLIKELY (fd == -1))
                return;

        while (len) {
-->             ret = pwrite (fd, data, len, offset);
[...]
}

(gdb) up
../src/vtestream-file.h:509

/*
 * offset is either within the stream (overwrite data), or at its head (append data).
 * data is at most VTE_SNAKE_BLOCKSIZE bytes large; if shorter then the remaining amount is skipped.
 * When reading back, that skipped area will contain garbage (e.g. when the FS doesn't support
 * punching holes), the caller needs to deal with it.
 *
 * When appending, the following state transfers can occur:
 * 1->2, 2->3.
 */
static void
_vte_snake_write (VteSnake *snake, gsize offset, const char *data, gsize len)
{
[...]
-->     _file_write (snake->fd, data, len, fd_offset);
}


(gdb) up
(gdb) up
(gdb) up
./src/ring.hh:151
        inline void append_row_record(RowRecord const* record,
                                      row_t position)
        {
-->             _vte_stream_append(m_row_stream,
                                   (char const*)record,
                                   sizeof(*record));
        }

(gdb) up
(gdb) up
(gdb) up
./src/ring.hh:254
static inline VteRowData *_vte_ring_insert (VteRing *ring, gulong position, guint8 bidi_flags) { return ring->insert(position, bidi_flags); }


(gdb) up
(gdb) up
(gdb) up
./src/vte.cc:2798
[...]
               } else {
                        /* Scroll up with history. */
                        m_screen->cursor.row++;
-->                     update_insert_delta();
                }
[...]


I think I can't go deeper, I don't know if vte is configurable to *not*
write his fd's to /tmp nor what those files are necessary at first place.

Some thing to check is where is the code that opens those /tmp files.

    * grep tmp vteutils.cc
        fd = open (g_get_tmp_dir (),
        /* Try again with g_file_open_tmp */
        fd = g_file_open_tmp ("vteXXXXXX", &file_name, NULL);
        /* Mark the tmpfile as no-cow on file systems that support it.

    * grep -RF _vte_mkstemp .
./vteutils.cc:_vte_mkstemp (void)
./vteutils.h:int _vte_mkstemp (void);
./vtestream-file.h:        snake->fd = _vte_mkstemp ();

#1012046#10
Date:
2022-05-29 10:51:37 UTC
From:
To:
Hi,

I just found a "solution" to get rid of the problem without recompiling anything : TMPFILE env var is taken into account. I have added a systemd override file for my user. It may be useful to have it globally by default in the distro.

$ systemctl --user cat gnome-terminal-server.service | tail
# /home/lpouzenc/.config/systemd/user/gnome-terminal-server.service.d/override.conf
[Service]
RuntimeDirectory=gnome-terminal-server
Environment=TMPDIR=%t/gnome-terminal-server

After closing and opening my gnome session again :

$ tr '' 'n' < /proc/$(pidof gnome-terminal-server)/environ | grep TMP
TMPDIR=/run/user/1000/gnome-terminal-server
$ lsof -np $(pidof gnome-terminal-server) | tail -n5
lsof: WARNING: can't stat() fuse.gvfsd-fuse file system /run/user/1000/gvfs
 Output information may be incomplete.
lsof: WARNING: can't stat() fuse.portal file system /run/user/1000/doc
 Output information may be incomplete.
gnome-ter 15142 lpouzenc 12u unix 0x00000000f567608e 0t0 252993 type=STREAM (CONNECTED)
gnome-ter 15142 lpouzenc 13u CHR 5,2 0t0 85 /dev/ptmx
gnome-ter 15142 lpouzenc 14u CHR 5,2 0t0 85 /dev/ptmx
gnome-ter 15142 lpouzenc 15u REG 0,52 458752 674 /run/user/1000/gnome-terminal-server/#674 (deleted)
gnome-ter 15142 lpouzenc 16u REG 0,52 65536 675 /run/user/1000/gnome-terminal-server/#675 (deleted)

Hope it could help others,
Cheers,
Ludovic

#1012046#15
Date:
2022-05-29 21:48:03 UTC
From:
To:
Hi,

Friends pointed me out to older bugs reports of the quite same problem
with libvte. Situation has changed since but it seems kept in the wrong
choices to me... Problems are there since 09/2009 (vte-0.21.6).

https://www.climagic.org/bugreports/libvte-scrollback-written-to-disk.html

It is pointing out that the suggestion I made in previous comment was
also made in 2015 and has drawbacks : [...] it is inherited by all child
processed launched inside the terminal which is probably not what they want.

https://bugzilla.gnome.org/show_bug.cgi?id=631685#c50

Regards,

#1012046#20
Date:
2023-04-28 19:37:48 UTC
From:
To:
Hi,

You don't need to worry about leaking confidential data. VTE stores the
scrollback data in encrypted files, and erases the encryption key from
memory as soon as the given terminal tab is closed. That is, if support for
encryption is compiled in (it is in Debian), which you can double check by
looking for "+GNUTLS" in gnome-terminal's About dialog, or in the output of
gnome-terminal --version.

Should you be interested in juicy technical details, see:
https://bugzilla.gnome.org/show_bug.cgi?id=664611
https://bugzilla.gnome.org/show_bug.cgi?id=738601
and the comments at the top of vtestream-file.h, a file which you have
already found and looked at.

VTE supports infinite scrollback, due to popular demand. Consuming
arbitrarily large amount of RAM, potentially even more than the physical
amount you have, plays very badly with the kernel's swapping and OOM
killer, it's basically unviable as opposed to storing it on disk which
works fine. That's the reason (in a nutshell) behind this solution.

cheers,
egmont