Skip to content

Commit

Permalink
Fix filesystem metadata of dom0-provided kernels
Browse files Browse the repository at this point in the history
All files in the dom0-provided kernel images should be owned by
root:root, not user:mock!  They also need to have proper SELinux
contexts to be bootable with SELinux enforcing.  Furthermore, an SELinux
relabel can still damage the filesystem if the policy changes in the
future; prevent that by marking the files immutable.

Fixes QubesOS/qubes-issues#4278
Fixes QubesOS/qubes-issues#5765
  • Loading branch information
DemiMarie committed Dec 6, 2021
1 parent 392f829 commit ed67c38
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 4 deletions.
121 changes: 121 additions & 0 deletions genattr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#!/usr/bin/python3 --
import os
import re
import subprocess
import sys
import tempfile
import stat

def main(argv, write_cmd, read_cmd, expected_stdout):
match_re = re.compile(r'\A[A-Za-z0-9._/+,-]+\Z')
write_cmd.write('set_current_time 1\n')
def bad(e):
raise e
def do_emit(path, context):
context = ':'.join(('system_u', 'object_r', context, 's0'))
write_cmd.write(f'ea_set /{path} security.selinux {context}\n'
f'set_inode_field /{path} uid 0\n'
f'set_inode_field /{path} gid 0\n')
read_cmd.write(f'ea_get /{path} security.selinux\n')
expected_stdout.write(f'''debugfs: ea_get /{path} security.selinux
security.selinux ({len(context)}) = "{context}"
''')
def emit(dirpath, name):
path = os.path.join(dirpath, name)
context = 'modules_object_t'
if path.startswith(argv + '/build'):
context = 'usr_t'
elif dirpath == argv:
if name.startswith('modules.'):
context = 'modules_dep_t'
elif name == 'build':
context = 'usr_t'
do_emit(path, context)
expected = sorted((argv, 'initramfs', 'vmlinuz', 'firmware'))
actual = sorted(os.listdir('.'))
#assert expected == actual, f'expected directory contents to be {expected!r} but it was {actual!r}'
assert os.listdir('firmware') == [argv]
def ftype(a): return os.lstat(a).st_mode
assert stat.S_ISREG(ftype('initramfs'))
assert stat.S_ISREG(ftype('vmlinuz'))
assert stat.S_ISDIR(ftype('firmware'))
assert stat.S_ISDIR(ftype('firmware/' + argv))
assert stat.S_ISDIR(ftype(argv))
okay = ('initramfs', 'vmlinuz', 'firmware/' + argv, 'firmware', 'lost+found', '', argv)
for i in okay:
if i and i != argv:
write_cmd.write(f'set_inode_field /{i} flags 16\n')
do_emit(i, 'modules_object_t')
for dirpath, dirnames, filenames in os.walk(argv, onerror=bad, topdown=True):
for i in dirnames:
if not match_re.match(i):
raise ValueError("Invalid directory name {os.path.join(dirpath, i)!r}")
emit(dirpath, i)
for i in filenames:
joined = os.path.join(dirpath, i)
if not match_re.match(i):
raise ValueError(f"Invalid file name {joined!r}")
if stat.S_ISREG(os.lstat(joined).st_mode):
if dirpath == argv and not i.startswith('modules.'):
write_cmd.write(f'set_inode_field {joined} flags 16\n')
emit(dirpath, i)
def outer_main(directory_with_files, image_file, directory_to_label):
subprocess.check_call(('/sbin/mkfs.ext3', '-F', '-Enum_backup_sb=0,hash_seed=dcee2318-92bd-47a5-a15d-e79d1412cdce,root_owner=0:0', '-d', directory_with_files,
'-U', 'dcee2318-92bd-47a5-a15d-e79d1412cdce', '--', image_file, '1024M'))
with tempfile.TemporaryDirectory() as d, \
open(os.path.join(d, 'write_cmd'), 'w') as write_cmd, \
open(os.path.join(d, 'read_cmd'), 'w') as read_cmd, \
open(os.path.join(d, 'expected_stdout'), 'w') as expected_stdout, \
open(os.path.join(d, 'actual_stdout'), 'w') as actual_stdout, \
open(os.path.join(d, 'clear_timestamps'), 'w') as clear_timestamps:
old_dir = os.open('.', os.O_RDONLY|os.O_DIRECTORY)
try:
os.chdir(directory_with_files)
main(directory_to_label, write_cmd, read_cmd, expected_stdout)
finally:
os.chdir(old_dir)
os.close(old_dir)
write_cmd.write("""
set_super_value mtime 1
set_super_value lastcheck 1
set_super_value mkfs_time 1
set_super_value first_error_time 0
set_super_value last_error_time 0
set_current_time 1
set_super_value wtime 1
dirty -clean
close
""")
clear_timestamps.write("""
set_super_value mtime 1
set_super_value lastcheck 1
set_super_value mkfs_time 1
set_super_value first_error_time 0
set_super_value last_error_time 0
set_current_time 1
set_super_value wtime 1
dirty -clean
close
""")
clear_timestamps.flush()
write_cmd.flush()
read_cmd.flush()
expected_stdout.flush()
subprocess.check_call(('debugfs', '-w', '-f', write_cmd.name, '--', image_file), stdout=subprocess.DEVNULL)
subprocess.check_call(('e2fsck', '-pDfE', 'optimize_extents', '--', image_file))
subprocess.check_call(('debugfs', '-w', '-f', clear_timestamps.name, '--', image_file))
subprocess.check_call(('resize2fs', '-M', '--', image_file))
subprocess.check_call(('e2fsck', '-pDfE', 'optimize_extents', '--', image_file))
subprocess.check_call(('debugfs', '-w', '-f', clear_timestamps.name, '--', image_file))
subprocess.check_call(('debugfs', '-f', read_cmd.name, '--', image_file), stdout=actual_stdout)
subprocess.check_call(('e2fsck', '-n', '--', image_file))
subprocess.check_call(('fallocate', '--dig-holes', '--', image_file))
# If the labels weren't set properly, fail the build
subprocess.check_call(('diff', '-u', '--', expected_stdout.name, actual_stdout.name))

if __name__ == '__main__':
if len(sys.argv) != 4:
print('Usage: genattry.py DIRECTORY-WITH-FILES IMAGE-FILE FOLDER-TO-LABEL', file=sys.stderr)
exit(1)
outer_main(sys.argv[1], sys.argv[2], sys.argv[3])
12 changes: 8 additions & 4 deletions kernel.spec.in
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Epoch: 1000
Release: %{rel}
License: GPL v2 only
Group: System/Kernel
Url: http://www.kernel.org/
Url: https://www.kernel.org/
AutoReqProv: on
BuildRequires: coreutils module-init-tools sparse
BuildRequires: qubes-kernel-vm-support
Expand Down Expand Up @@ -116,6 +116,7 @@ Source33: check-for-config-changes
Source34: gen-config
Source100: config-base
Source101: config-qubes
Source102: genattr.py
%define modsign_cmd %{SOURCE18}

Patch0: 0001-xen-netfront-detach-crash.patch
Expand Down Expand Up @@ -502,9 +503,12 @@ if [ -n "$SOURCE_DATE_EPOCH" ]; then
find %buildroot%vm_install_dir/modules \
-exec touch --no-dereference --date="@${SOURCE_DATE_EPOCH}" {} +
fi
PATH="/sbin:$PATH" mkfs.ext3 -d %buildroot%vm_install_dir/modules \
-U dcee2318-92bd-47a5-a15d-e79d1412cdce \
%buildroot%vm_install_dir/modules.img 1024M

# Actually generate the filesystem
PATH="/sbin:$PATH" python3 %SOURCE102 %buildroot%vm_install_dir/modules \
%buildroot%vm_install_dir/modules.img \
%{VERSION}-%{RELEASE}.%{_arch} || exit

rm -rf %buildroot%vm_install_dir/modules

# remove files that will be auto generated by depmod at rpm -i time
Expand Down

0 comments on commit ed67c38

Please sign in to comment.