summaryrefslogtreecommitdiffstats
path: root/config/chroot_local-includes/usr/local/sbin/tails-debugging-info
blob: 883c88a29d766357cfb7c5a182d8de21620cd76f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#! /usr/bin/env python3
"""
Debug Tails.

Test with "python3 tails-debugging-info.py doctest" as root.

goodcrypto.com converted from bash to python and added basic tests.

*** WARNING about debug_file and debug_directory *********************

Great attention must be given to the ownership situation of these
files and their parent directories in order to avoid a symlink-based
attack that could read the contents of any file and make it
accessible to the user running this script (typically the live
user). Therefore, when adding a new file, give as the first argument
'root' only if the complete path to it (including the file itself)
is owned by root and already exists before the system is connected to
the network (that is, before GDM's PostLogin script is run).
If not, the following rules must be followed strictly:

* only one non-root user is involved in the ownership situation (the
  file, its dir and the parent dirs). From now on let's assume it is
  the case and call it $USER.

* if any non-root group has write access, it must not have any
  members.

If any of these rules does not apply, the file cannot be added here
safely and something is probably quite wrong and should be
investigated carefully.

>>> # run script
>>> import sh
>>> this_command = sh.Command(sys.argv[0])
>>> this_command()
<BLANKLINE>
...
"""

import os
import sys
from pwd import getpwuid
import subprocess


# AppArmor Ux rules don't sanitize PATH, which can lead to an
# exploited application (that's allowed to run this script unconfined)
# having this script run arbitrary code, violating that application's
# confinement. Let's prevent that by setting PATH to a list of
# directories where only root can write.
os.environ['PATH'] = '/usr/local/bin:/usr/bin:/bin'


def main():
    """Print debug information.

    >>> main()
    <BLANKLINE>
    ...
    """

    debug_file('root', '/proc/cmdline')

    # General hardware and filesystems information
    debug_command('/usr/sbin/dmidecode', '-s', 'system-manufacturer')
    debug_command('/usr/sbin/dmidecode', '-s', 'system-product-name')
    debug_command('/usr/sbin/dmidecode', '-s', 'system-version')
    debug_command('/usr/bin/lspci', '-nn')
    debug_command('/bin/df', '--human-readable', '--print-type')
    debug_command('/bin/mount', '--show-labels')
    debug_command('/bin/lsmod')
    debug_file('root', '/proc/asound/cards')
    debug_file('root', '/proc/asound/devices')
    debug_file('root', '/proc/asound/modules')

    # Miscellaneous configuration and log files
    debug_file('root', '/etc/X11/xorg.conf')
    debug_file('Debian-gdm', '/var/log/gdm3/tails-greeter.errors')
    debug_file('root', '/var/log/live/boot.log')
    debug_file('root', '/var/log/live/config.log')
    debug_file('root', '/var/lib/live/config/tails.physical_security')

    # Persistence
    debug_file('root', '/var/lib/gdm3/tails.persistence')
    debug_file('tails-persistence-setup', '/live/persistence/TailsData_unlocked/persistence.conf')
    debug_file('tails-persistence-setup', '/live/persistence/TailsData_unlocked/live-additional-software.conf')
    debug_directory('root', '/live/persistence/TailsData_unlocked/apt-sources.list.d')
    debug_file('root', '/var/log/live-persist')

    # The Journal
    debug_command('/bin/journalctl', '--catalog', '--no-pager')


def debug_command(command, *args):
    """Print the command and then run it.

    >>> debug_command('echo', 'foo')
    <BLANKLINE>
    ===== output of command echo foo =====
    foo
    """
    print()
    print('===== output of command {} ====='.format(' '.join((command,) + args)))
    print(subprocess.check_output([command, *args]).decode().strip())


def debug_file(user, filename):
    """Print file content.

    >>> import tempfile, getpass
    >>> with tempfile.NamedTemporaryFile('w') as f:
    ...     _ = f.write("foo\\nbar")
    ...     _ = f.seek(0)
    ...     debug_file(getpass.getuser(), f.name)
    <BLANKLINE>
    ===== content of ... =====
    foo
    bar
    """
    if not os.path.isfile(filename):
        return

    # This check is not sufficient, see the comment at the top of the file
    # for the complete requirements required for security
    owner = getpwuid(os.stat(filename).st_uid).pw_name
    if owner != user:
        print()
        print('WARNING: not opening file {}, '.format(filename), end='')
        print('because it is owned by {} instead of {}'.format(owner, user))
        return

    print()
    print('===== content of {} ====='.format(filename))
    with open(filename) as f:
        print(f.read(), end='')


def debug_directory(user, dir_name):
    """List directory and print content of all contained files (non-recursively).

    >>> import tempfile, getpass
    >>> with tempfile.TemporaryDirectory() as tmpdir:
    ...     open(os.path.join(tmpdir, 'foo'), 'w').close()
    ...     debug_directory(getpass.getuser(), tmpdir)
    <BLANKLINE>
    ===== listing of ... =====
    foo
    """
    if not os.path.isdir(dir_name):
        return

    print()

    # This check is not sufficient, see the comment at the top of the file
    # for the complete requirements required for security
    owner = getpwuid(os.stat(dir_name).st_uid).pw_name
    if owner != user:
        print('WARNING: not opening directory {}, '.format(dir_name), end='')
        print('because it is owned by {} instead of {}'.format(owner, user))
        return

    files = os.listdir(dir_name)

    print('===== listing of {} ====='.format(dir_name))
    for f in files:
        print(f)

    for f in files:
        debug_file(user, f)


if __name__ == '__main__':
    if sys.argv and len(sys.argv) > 1:
        if sys.argv[1] == 'doctest':
            import doctest
            doctest.testmod(optionflags=doctest.ELLIPSIS)
        else:
            main()
    else:
        main()