Skip to content

Commit

Permalink
[wip] sign kernel modules
Browse files Browse the repository at this point in the history
This is a WIP/Proof-of-Concept change to introduce kernel
module signing.
The actual signing is performed by the in-tree script
sign-file.
The private key and the certificate must be provided in
the same directory as the libs/ checkout, under a specially
created directory signed-key/, i.e.

 + agent-libs
   + cmake
   + driver
   + proposals
   + signing-key   <-----
   + test
   + userspace

and must be called:
   + key.priv (private key in PEM format)
   + key.der  (certificate in DER format)

NOTE: this was done in order not to create any additional
paths to mount as volume, (first to the outer container,
and then to the specific container).

ALSO NOTE: on newer kernels the sign-file would be a C program which
also accepts a PEM textual file for the certificate but older kernels
use a perl script which can only read a DER binary file,
so for compatibility reason this _must_ be a binary file.

The top-level python script must be called with the '-x/--sign' flag
in order for module signing to be enabled.
Since different kernels might use a different hashing algorithm
(sha256, sha512, etc..), the one chosen by the target kernel is
read from the kernel config file under CONFIG_MODULE_SIG_HASH.

This value is then passed over to the downstream builder
as SIGN_FILE_HASH_ALGO.
When '-x/--sign' is not specified, this is left empty and the
module is NOT signed.
  • Loading branch information
iurly committed Feb 23, 2023
1 parent 291f491 commit 6800942
Show file tree
Hide file tree
Showing 7 changed files with 50 additions and 9 deletions.
1 change: 1 addition & 0 deletions Dockerfile.centos-gcc4.8
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ RUN yum -y install \
flex \
make \
cmake \
openssl \
elfutils-devel \
python-lxml && yum clean all

Expand Down
11 changes: 11 additions & 0 deletions builder-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
# PROBE_DEVICE_NAME
# PROBE_NAME
# PROBE_VERSION
# SIGN_FILE_HASH_ALGO

# optional env vars
# CLANG
Expand Down Expand Up @@ -56,6 +57,16 @@ build_kmod() {
exit 1
fi

if [ -n "${SIGN_FILE_HASH_ALGO}" ]; then
echo "Signing generated kernel module"
${KERNELDIR}/scripts/sign-file ${SIGN_FILE_HASH_ALGO} \
/code/sysdig-ro/signing-key/key.priv \
/code/sysdig-ro/signing-key/key.der \
driver/${PROBE_NAME}.ko
else
echo "Kernel module will NOT be signed"
fi

cp driver/$PROBE_NAME.ko $OUTPUT/$PROBE_NAME-$PROBE_VERSION-$ARCH-$KERNEL_RELEASE-$HASH.ko
cp driver/$PROBE_NAME.ko $OUTPUT/$PROBE_NAME-$PROBE_VERSION-$ARCH-$KERNEL_RELEASE-$HASH_ORIG.ko
}
Expand Down
5 changes: 3 additions & 2 deletions probe_builder/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,11 @@ def cli(debug):
@click.option('-s', '--source-dir')
@click.option('-t', '--download-timeout', type=click.FLOAT)
@click.option('-v', '--probe-version')
@click.option('-x', '--sign', is_flag=True)
@click.argument('package', nargs=-1)
def build(builder_image_prefix,
download_concurrency, jobs, kernel_type, filter, probe_name, retries,
source_dir, download_timeout, probe_version, package):
source_dir, download_timeout, probe_version, sign, package):
workspace_dir = os.getcwd()
builder_source = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

Expand All @@ -135,7 +136,7 @@ def build(builder_image_prefix,
with ThreadPoolExecutor(max_workers=jobs) as executor:
kernels_futures = []
for release, target in kernel_dirs:
future = executor.submit(distro_builder.build_kernel, workspace, probe, distro.builder_distro, release, target)
future = executor.submit(distro_builder.build_kernel, workspace, probe, distro.builder_distro, release, target, sign)
kernels_futures.append((release, future))


Expand Down
5 changes: 3 additions & 2 deletions probe_builder/builder/builder_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def build(workspace, dockerfile, dockerfile_tag):
return obj

def run(workspace, probe, kernel_dir, kernel_release,
config_hash, container_name, image_name, args):
config_hash, container_name, image_name, sign_file_hash_algo, args):
volumes = [
docker.DockerVolume(workspace.host_dir(probe.sysdig_dir), '/code/sysdig-ro', True),
docker.DockerVolume(workspace.host_workspace(), '/build/probe', True),
Expand All @@ -53,7 +53,8 @@ def run(workspace, probe, kernel_dir, kernel_release,
docker.EnvVar('KERNELDIR', kernel_dir.replace(workspace.workspace, '/build/probe/')),
docker.EnvVar('KERNEL_RELEASE', kernel_release),
docker.EnvVar('HASH', config_hash),
docker.EnvVar('HASH_ORIG', config_hash)
docker.EnvVar('HASH_ORIG', config_hash),
docker.EnvVar('SIGN_FILE_HASH_ALGO', sign_file_hash_algo),
]

return docker.run(image_name, volumes, args, env, name=container_name)
Expand Down
23 changes: 18 additions & 5 deletions probe_builder/builder/distro/base_builder.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import configparser
import errno
import logging
import os
Expand Down Expand Up @@ -61,17 +62,28 @@ def md5sum(path):
digest.update(fp.read().encode('utf-8'))
return digest.hexdigest()

@staticmethod
def config_module_sig_hash(path):
with open(path, 'r') as f:
config_string = '[DEFAULT]\n' + f.read()
config = configparser.ConfigParser()
config.read_string(config_string)
return config['DEFAULT']['CONFIG_MODULE_SIG_HASH'].replace('"','')

def unpack_kernels(self, workspace, distro, kernels):
raise NotImplementedError

def hash_config(self, release, target):
raise NotImplementedError

def sign_file_hash_algo(self, release, target):
raise NotImplementedError

def get_kernel_dir(self, workspace, release, target):
raise NotImplementedError

@classmethod
def build_kernel_impl(cls, config_hash, container_name, image_name, kernel_dir, probe, release, workspace, bpf,
def build_kernel_impl(cls, config_hash, sign_file_hash_algo, container_name, image_name, kernel_dir, probe, release, workspace, bpf,
skip_reason):
if bpf:
label = 'eBPF'
Expand All @@ -91,7 +103,7 @@ def build_kernel_impl(cls, config_hash, container_name, image_name, kernel_dir,
#docker.rm(container_name)
try:
ts0 = time.time()
stdout = builder_image.run(workspace, probe, kernel_dir, release, config_hash, container_name, image_name, args)
stdout = builder_image.run(workspace, probe, kernel_dir, release, config_hash, container_name, image_name, sign_file_hash_algo, args)
except subprocess.CalledProcessError as e:
took = time.time() - ts0
logger.error("Build failed for {} probe {}-{} (took {:.3f}s)".format(label, release, config_hash, took))
Expand All @@ -107,8 +119,9 @@ def build_kernel_impl(cls, config_hash, container_name, image_name, kernel_dir,
logger.warn(make_string(line))
return cls.ProbeBuildResult(cls.ProbeBuildResult.BUILD_FAILED, took, stdout)

def build_kernel(self, workspace, probe, builder_distro, release, target):
def build_kernel(self, workspace, probe, builder_distro, release, target, do_sign=False):
config_hash = self.hash_config(release, target)
sign_file_hash_algo = self.sign_file_hash_algo(release, target) if do_sign else ""
output_dir = workspace.subdir('output')

kmod_skip_reason = builder_image.skip_build(probe, output_dir, release, config_hash, False)
Expand Down Expand Up @@ -138,9 +151,9 @@ def build_kernel(self, workspace, probe, builder_distro, release, target):
container_name = ''

return self.KernelBuildResult(
self.build_kernel_impl(config_hash, container_name, image_name, kernel_dir, probe, release, workspace, False,
self.build_kernel_impl(config_hash, sign_file_hash_algo, container_name, image_name, kernel_dir, probe, release, workspace, False,
kmod_skip_reason),
self.build_kernel_impl(config_hash, container_name, image_name, kernel_dir, probe, release, workspace, True,
self.build_kernel_impl(config_hash, sign_file_hash_algo, container_name, image_name, kernel_dir, probe, release, workspace, True,
ebpf_skip_reason),
)

Expand Down
11 changes: 11 additions & 0 deletions probe_builder/builder/distro/centos.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ def hash_config(self, release, target):
except IOError:
return self.md5sum(os.path.join(target, 'lib/modules/{}/config'.format(release)))

def sign_file_hash_algo(self, release, target):
boot = os.path.join(target, 'boot/config-{}'.format(release))
lib_modules = os.path.join(target, 'lib/modules/{}/config'.format(release))

if os.path.isfile(boot):
return self.config_module_sig_hash(boot)
elif os.path.isfile(lib_modules):
return self.config_module_sig_hash(lib_modules)
else:
raise Exception("No file found!")

def get_kernel_dir(self, workspace, release, target):
return workspace.subdir(target, 'usr/src/kernels', release)

Expand Down
3 changes: 3 additions & 0 deletions probe_builder/builder/distro/debian.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ def unpack_kernels(self, workspace, distro, kernels):
def hash_config(self, release, target):
return self.md5sum(os.path.join(target, 'boot/config-{}'.format(release)))

def sign_file_hash_algo(self, release, target):
return self.config_module_sig_hash(os.path.join(target, 'boot/config-{}'.format(release)))

def get_kernel_dir(self, workspace, release, target):
return workspace.subdir(target, 'usr/src/linux-headers-{}'.format(release))

Expand Down

0 comments on commit 6800942

Please sign in to comment.