Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Record and restore SELinux context for mocked /dev nodes #222

Merged
merged 2 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ scripts/ioctls, etc.), unless it is a feature request.
License
=======
- Copyright (C) 2012 - 2014 Canonical Ltd.
- Copyright (C) 2017 - 2021 Martin Pitt
- Copyright (C) 2017 - 2023 Martin Pitt

umockdev is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
Expand Down
32 changes: 22 additions & 10 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,15 @@ meson.add_dist_script(srcdir / 'getversion.sh')
# dependencies
#

optional_defines = []

dl = cc.find_library('dl')
selinux = cc.find_library('libselinux', required: false)
if selinux.found()
if cc.check_header('selinux/selinux.h')
optional_defines += ['--define=HAVE_SELINUX']
endif
endif

glib = dependency('glib-2.0', version: '>= 2.32.0')
gobject = dependency('gobject-2.0', version: '>= 2.32.0')
Expand All @@ -87,6 +95,7 @@ vala_libutil = cc.find_library('util')
# local VAPIs
vapi_config = valac.find_library('config', dirs: srcdir)
vapi_ioctl = valac.find_library('ioctl', dirs: srcdir)
vapi_selinux = valac.find_library('selinux', dirs: srcdir)
vapi_assertions = valac.find_library('assertions', dirs: testsdir)

#
Expand Down Expand Up @@ -141,7 +150,7 @@ umockdev_lib = shared_library('umockdev',
'src/debug.c'],
vala_vapi: 'umockdev-1.0.vapi',
vala_gir: 'UMockdev-1.0.gir',
dependencies: [glib, gobject, gio, gio_unix, vapi_posix, vapi_linux, vapi_linux_fixes, vala_libudev, vala_libutil, vapi_ioctl, libpcap],
dependencies: [glib, gobject, gio, gio_unix, vapi_posix, vapi_linux, vapi_linux_fixes, vala_libudev, vala_libutil, vapi_ioctl, vapi_selinux, libpcap, selinux],
link_with: [umockdev_utils_lib],
link_depends: ['src/umockdev.map'],
link_args: [
Expand All @@ -151,7 +160,7 @@ umockdev_lib = shared_library('umockdev',
],
vala_args: ['--define=INTERNAL_REGISTER_API',
'--define=INTERNAL_UNREGISTER_PATH_API',
'--vapidir=@0@/src'.format(meson.current_source_dir())],
'--vapidir=@0@/src'.format(meson.current_source_dir())] + optional_defines,
include_directories: include_directories('src'),
version: lib_version,
install: true,
Expand Down Expand Up @@ -201,11 +210,11 @@ umockdev_record_exe = executable('umockdev-record',
'src/ioctl_tree.c',
'src/utils.c',
'src/debug.c'],
dependencies: [glib, gobject, gio_unix, vapi_posix, vapi_config, vapi_ioctl, libpcap],
dependencies: [glib, gobject, gio_unix, vapi_posix, vapi_config, vapi_ioctl, vapi_selinux, libpcap, selinux],
link_with: [umockdev_utils_lib],
vala_args: ['--define=INTERNAL_REGISTER_API',
'--define=INTERNAL_UNREGISTER_ALL_API',
'--vapidir=@0@/src'.format(meson.current_source_dir())],
'--vapidir=@0@/src'.format(meson.current_source_dir())] + optional_defines,
include_directories: include_directories('src'),
install: true)

Expand Down Expand Up @@ -257,8 +266,9 @@ if gudev.found()

test('umockdev-vala', executable('test-umockdev-vala',
'tests/test-umockdev-vala.vala',
dependencies: [glib, gobject, gio, gudev, vapi_posix, vapi_assertions, vapi_ioctl],
link_with: [umockdev_lib, umockdev_utils_lib]),
dependencies: [glib, gobject, gio, gudev, vapi_posix, vapi_assertions, vapi_ioctl, vapi_selinux, selinux],
link_with: [umockdev_lib, umockdev_utils_lib],
vala_args: optional_defines),
depends: [preload_lib],
suite: 'fails-valgrind')
endif
Expand All @@ -273,14 +283,16 @@ test('ioctl-tree', executable('test-ioctl-tree',

test('umockdev-run', executable('test-umockdev-run',
'tests/test-umockdev-run.vala',
dependencies: [glib, gobject, gio, vapi_posix, vapi_assertions, vapi_config],
link_with: [umockdev_lib, umockdev_utils_lib]),
dependencies: [glib, gobject, gio, vapi_posix, vapi_assertions, vapi_config, vapi_selinux, selinux],
link_with: [umockdev_lib, umockdev_utils_lib],
vala_args: optional_defines),
depends: [umockdev_run_exe, preload_lib, test_chatter_exe, test_chatter_stream_exe])

test('umockdev-record', executable('test-umockdev-record',
'tests/test-umockdev-record.vala',
dependencies: [glib, gobject, gio, gio_unix, vapi_posix, vapi_linux, vapi_assertions, vapi_config, vala_libutil],
link_with: [umockdev_lib, umockdev_utils_lib]),
dependencies: [glib, gobject, gio, gio_unix, vapi_posix, vapi_linux, vapi_assertions, vapi_config, vala_libutil, vapi_selinux, selinux],
link_with: [umockdev_lib, umockdev_utils_lib],
vala_args: optional_defines),
depends: [umockdev_record_exe, preload_lib, test_readbyte_exe, test_chatter_exe, test_chatter_stream_exe],
suite: 'fails-valgrind')

Expand Down
5 changes: 5 additions & 0 deletions src/selinux.vapi
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[CCode (cprefix = "", lower_case_cprefix = "", cheader_filename = "selinux/selinux.h")]
namespace Selinux {
int lgetfilecon (string path, out string context);
int lsetfilecon (string path, string context);
}
14 changes: 13 additions & 1 deletion src/umockdev-record.vala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
*/

using UMockdevUtils;
#if HAVE_SELINUX
using Selinux;
#endif

static void
devices_from_dir (string dir, ref GenericArray<string> devs)
Expand Down Expand Up @@ -251,7 +254,16 @@ record_device(string dev)
continue;

if (line.has_prefix("N: ")) {
line = line + dev_contents("/dev/" + line.substring(3).chomp());
string devpath = "/dev/" + line.substring(3).chomp();
line = line + dev_contents(devpath);

// record SELinux context
#if HAVE_SELINUX
string context; // this is owned by vala, not calling Selinux.freecon() on it
int res = Selinux.lgetfilecon(devpath, out context);
if (res > 0)
properties.append("E: __DEVCONTEXT=" + context);
#endif
}
stdout.puts(line);
stdout.putc('\n');
Expand Down
37 changes: 35 additions & 2 deletions src/umockdev.vala
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ namespace UMockdev {

using UMockdevUtils;

#if HAVE_SELINUX
using Selinux;
#endif

private bool __in_mock_env_initialized = false;
private bool __in_mock_env_result = false;

Expand Down Expand Up @@ -538,6 +542,9 @@ public class Testbed: GLib.Object {
* possible to change them later on with umockdev_testbed_set_attribute() and
* umockdev_testbed_set_property().
*
* If the pseudo-property "__DEVCONTEXT" is present, the SELinux context of the device's
* DEVNODE will be set to that value.
*
* This will synthesize an "add" uevent.
*
* Returns: The sysfs path for the newly created device. Free with g_free().
Expand Down Expand Up @@ -577,6 +584,9 @@ public class Testbed: GLib.Object {
* possible to change them later on with umockdev_testbed_set_attribute() and
* umockdev_testbed_set_property().
*
* If the pseudo-property "__DEVCONTEXT" is present, the SELinux context of the device's
* DEVNODE will be set to that value.
*
* This will synthesize an "add" uevent.
*
* Example:
Expand Down Expand Up @@ -1300,6 +1310,7 @@ public class Testbed: GLib.Object {
string[] binattrs = {}; /* hex encoded values */
string[] linkattrs = {};
string[] props = {};
string? selinux_context = null;

/* scan until we see an empty line */
while (cur_data.length > 0 && cur_data[0] != '\n') {
Expand Down Expand Up @@ -1328,6 +1339,14 @@ public class Testbed: GLib.Object {
break;

case 'E':
if (key == "__DEVCONTEXT") {
if (selinux_context != null)
throw new UMockdev.Error.VALUE("duplicate __DEVCONTEXT property in description of device %s",
devpath);
selinux_context = val;
break;
}

props += key;
props += val;
if (key == "SUBSYSTEM") {
Expand Down Expand Up @@ -1378,7 +1397,7 @@ public class Testbed: GLib.Object {

/* create fake device node */
if (devnode_path != null) {
this.create_node_for_device(subsystem, devnode_path, devnode_contents, majmin);
this.create_node_for_device(subsystem, devnode_path, devnode_contents, majmin, selinux_context);

/* create symlinks */
for (int i = 0; i < devnode_links.length; i++) {
Expand All @@ -1400,7 +1419,8 @@ public class Testbed: GLib.Object {
}

private void
create_node_for_device (string subsystem, string node_path, uint8[] node_contents, string? majmin)
create_node_for_device (string subsystem, string node_path, uint8[] node_contents, string? majmin,
string? selinux_context)
throws UMockdev.Error
{
checked_mkdir_with_parents(Path.get_dirname(node_path), 0755);
Expand All @@ -1418,6 +1438,7 @@ public class Testbed: GLib.Object {
error("Cannot create dev node file: %s", e.message);
}

set_selinux_context (node_path, selinux_context);
return;
}

Expand Down Expand Up @@ -1456,8 +1477,20 @@ public class Testbed: GLib.Object {
string devname = node_path.substring (this.root_dir.length);
assert (!this.dev_fd.contains (devname));
this.dev_fd.insert (devname, ptym);

set_selinux_context (node_path, selinux_context);
}

private void set_selinux_context (string path, string? context)
{
#if HAVE_SELINUX
if (context != null) {
// this is opportunistic, it needs to work in environments without privilegs or SELinux
if (Selinux.lsetfilecon (path, context) < 0)
debug ("umockdev Testbed.create_node_for_device: setfilecon(%s, %s) failed: %m", path, context);
}
#endif
}

/**
* umockdev_testbed_record_parse_line:
Expand Down
9 changes: 8 additions & 1 deletion tests/run-nix
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,12 @@ EOG

[ -z "${DEBUG:-}" ] || nix-env -i cntr

nix-build --keep-failed /tmp/default.nix
if ! nix-build --keep-failed /tmp/default.nix; then
for log in /build/source/build/meson-logs/*; do
[ -f "\$log" ] || break
echo "=== \$log ==="
cat "\$log"
done
exit 1
fi
EOF
14 changes: 14 additions & 0 deletions tests/test-umockdev-record.vala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
using UMockdevUtils;
using Assertions;

#if HAVE_SELINUX
using Selinux;
#endif

string readbyte_path;
string tests_dir;

Expand Down Expand Up @@ -196,6 +200,16 @@ t_system_single ()
assert_in("E: DEVNAME=/dev/null", sout);
assert_in("P: /devices/virtual/mem/null", sout);
assert_in("E: DEVNAME=/dev/zero", sout);
#if HAVE_SELINUX
// we may run on a system without SELinux
if (FileUtils.test("/sys/fs/selinux", FileTest.EXISTS)) {
string context;
assert_cmpint (Selinux.lgetfilecon ("/dev/null", out context), CompareOperator.GT, 0);
assert_in("E: __DEVCONTEXT=" + context + "\n", sout);
} else {
assert(!sout.contains("E: __DEVCONTEXT"));
}
#endif
}

// system /sys: umockdev-record --all works and result loads back
Expand Down
30 changes: 30 additions & 0 deletions tests/test-umockdev-run.vala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
using UMockdevUtils;
using Assertions;

#if HAVE_SELINUX
using Selinux;
#endif

const string umockdev_run_command = "env LC_ALL=C umockdev-run ";
const string umockdev_record_command = "env LC_ALL=C umockdev-record ";

Expand Down Expand Up @@ -180,6 +184,7 @@ t_run_udevadm_block ()
checked_file_set_contents (umockdev_file, """P: /devices/virtual/block/loop23
N: loop23
E: DEVNAME=/dev/loop23
E: __DEVCONTEXT=system_u:object_r:fixed_disk_device_t:s0
E: DEVTYPE=disk
E: MAJOR=7
E: MINOR=23
Expand Down Expand Up @@ -207,6 +212,18 @@ A: size=1048576\n
assert (sout.contains ("E: MAJOR=7"));
assert (sout.contains ("E: MINOR=23"));

#if HAVE_SELINUX
// we may run on a system without SELinux
if (FileUtils.test("/sys/fs/selinux", FileTest.EXISTS)) {
check_program_out("true", "-d " + umockdev_file + " -- stat -c %C /dev/loop23",
"system_u:object_r:fixed_disk_device_t:s0\n");
} else {
stdout.printf ("[SKIP selinux context check: SELinux not active] ");
}
#else
stdout.printf ("[SKIP selinux context check: not built with SELinux support] ");
#endif

checked_remove (umockdev_file);
}

Expand Down Expand Up @@ -333,6 +350,19 @@ t_run_record_null ()
check_program_out("true", "-d " + umockdev_file + " -- stat -c '%n %F %t %T' /dev/null",
"/dev/null character special file 1 3\n");

#if HAVE_SELINUX
// we may run on a system without SELinux
if (FileUtils.test("/sys/fs/selinux", FileTest.EXISTS)) {
string orig_context;
assert_cmpint (Selinux.lgetfilecon ("/dev/null", out orig_context), CompareOperator.GT, 0);
check_program_out("true", "-d " + umockdev_file + " -- stat -c %C /dev/null", orig_context + "\n");
} else {
stdout.printf ("[SKIP selinux context check: SELinux not active] ");
}
#else
stdout.printf ("[SKIP selinux context check: not built with SELinux support] ");
#endif

checked_remove (umockdev_file);
}

Expand Down
Loading