Merge a430d95c5e ("Merge tag 'lsm-pr-20240911' of git://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/lsm") into android-mainline
Steps on the way to 6.12-rc1 Bug: 367265496 Change-Id: I2d1107f51e1fc2cf7df866a66009097a1f26061f Signed-off-by: Matthias Maennich <maennich@google.com>
This commit is contained in:
@@ -47,3 +47,4 @@ subdirectories.
|
||||
tomoyo
|
||||
Yama
|
||||
SafeSetID
|
||||
ipe
|
||||
|
||||
@@ -0,0 +1,790 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
Integrity Policy Enforcement (IPE)
|
||||
==================================
|
||||
|
||||
.. NOTE::
|
||||
|
||||
This is the documentation for admins, system builders, or individuals
|
||||
attempting to use IPE. If you're looking for more developer-focused
|
||||
documentation about IPE please see :doc:`the design docs </security/ipe>`.
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
Integrity Policy Enforcement (IPE) is a Linux Security Module that takes a
|
||||
complementary approach to access control. Unlike traditional access control
|
||||
mechanisms that rely on labels and paths for decision-making, IPE focuses
|
||||
on the immutable security properties inherent to system components. These
|
||||
properties are fundamental attributes or features of a system component
|
||||
that cannot be altered, ensuring a consistent and reliable basis for
|
||||
security decisions.
|
||||
|
||||
To elaborate, in the context of IPE, system components primarily refer to
|
||||
files or the devices these files reside on. However, this is just a
|
||||
starting point. The concept of system components is flexible and can be
|
||||
extended to include new elements as the system evolves. The immutable
|
||||
properties include the origin of a file, which remains constant and
|
||||
unchangeable over time. For example, IPE policies can be crafted to trust
|
||||
files originating from the initramfs. Since initramfs is typically verified
|
||||
by the bootloader, its files are deemed trustworthy; "file is from
|
||||
initramfs" becomes an immutable property under IPE's consideration.
|
||||
|
||||
The immutable property concept extends to the security features enabled on
|
||||
a file's origin, such as dm-verity or fs-verity, which provide a layer of
|
||||
integrity and trust. For example, IPE allows the definition of policies
|
||||
that trust files from a dm-verity protected device. dm-verity ensures the
|
||||
integrity of an entire device by providing a verifiable and immutable state
|
||||
of its contents. Similarly, fs-verity offers filesystem-level integrity
|
||||
checks, allowing IPE to enforce policies that trust files protected by
|
||||
fs-verity. These two features cannot be turned off once established, so
|
||||
they are considered immutable properties. These examples demonstrate how
|
||||
IPE leverages immutable properties, such as a file's origin and its
|
||||
integrity protection mechanisms, to make access control decisions.
|
||||
|
||||
For the IPE policy, specifically, it grants the ability to enforce
|
||||
stringent access controls by assessing security properties against
|
||||
reference values defined within the policy. This assessment can be based on
|
||||
the existence of a security property (e.g., verifying if a file originates
|
||||
from initramfs) or evaluating the internal state of an immutable security
|
||||
property. The latter includes checking the roothash of a dm-verity
|
||||
protected device, determining whether dm-verity possesses a valid
|
||||
signature, assessing the digest of a fs-verity protected file, or
|
||||
determining whether fs-verity possesses a valid built-in signature. This
|
||||
nuanced approach to policy enforcement enables a highly secure and
|
||||
customizable system defense mechanism, tailored to specific security
|
||||
requirements and trust models.
|
||||
|
||||
To enable IPE, ensure that ``CONFIG_SECURITY_IPE`` (under
|
||||
:menuselection:`Security -> Integrity Policy Enforcement (IPE)`) config
|
||||
option is enabled.
|
||||
|
||||
Use Cases
|
||||
---------
|
||||
|
||||
IPE works best in fixed-function devices: devices in which their purpose
|
||||
is clearly defined and not supposed to be changed (e.g. network firewall
|
||||
device in a data center, an IoT device, etcetera), where all software and
|
||||
configuration is built and provisioned by the system owner.
|
||||
|
||||
IPE is a long-way off for use in general-purpose computing: the Linux
|
||||
community as a whole tends to follow a decentralized trust model (known as
|
||||
the web of trust), which IPE has no support for it yet. Instead, IPE
|
||||
supports PKI (public key infrastructure), which generally designates a
|
||||
set of trusted entities that provide a measure of absolute trust.
|
||||
|
||||
Additionally, while most packages are signed today, the files inside
|
||||
the packages (for instance, the executables), tend to be unsigned. This
|
||||
makes it difficult to utilize IPE in systems where a package manager is
|
||||
expected to be functional, without major changes to the package manager
|
||||
and ecosystem behind it.
|
||||
|
||||
The digest_cache LSM [#digest_cache_lsm]_ is a system that when combined with IPE,
|
||||
could be used to enable and support general-purpose computing use cases.
|
||||
|
||||
Known Limitations
|
||||
-----------------
|
||||
|
||||
IPE cannot verify the integrity of anonymous executable memory, such as
|
||||
the trampolines created by gcc closures and libffi (<3.4.2), or JIT'd code.
|
||||
Unfortunately, as this is dynamically generated code, there is no way
|
||||
for IPE to ensure the integrity of this code to form a trust basis.
|
||||
|
||||
IPE cannot verify the integrity of programs written in interpreted
|
||||
languages when these scripts are invoked by passing these program files
|
||||
to the interpreter. This is because the way interpreters execute these
|
||||
files; the scripts themselves are not evaluated as executable code
|
||||
through one of IPE's hooks, but they are merely text files that are read
|
||||
(as opposed to compiled executables) [#interpreters]_.
|
||||
|
||||
Threat Model
|
||||
------------
|
||||
|
||||
IPE specifically targets the risk of tampering with user-space executable
|
||||
code after the kernel has initially booted, including the kernel modules
|
||||
loaded from userspace via ``modprobe`` or ``insmod``.
|
||||
|
||||
To illustrate, consider a scenario where an untrusted binary, possibly
|
||||
malicious, is downloaded along with all necessary dependencies, including a
|
||||
loader and libc. The primary function of IPE in this context is to prevent
|
||||
the execution of such binaries and their dependencies.
|
||||
|
||||
IPE achieves this by verifying the integrity and authenticity of all
|
||||
executable code before allowing them to run. It conducts a thorough
|
||||
check to ensure that the code's integrity is intact and that they match an
|
||||
authorized reference value (digest, signature, etc) as per the defined
|
||||
policy. If a binary does not pass this verification process, either
|
||||
because its integrity has been compromised or it does not meet the
|
||||
authorization criteria, IPE will deny its execution. Additionally, IPE
|
||||
generates audit logs which may be utilized to detect and analyze failures
|
||||
resulting from policy violation.
|
||||
|
||||
Tampering threat scenarios include modification or replacement of
|
||||
executable code by a range of actors including:
|
||||
|
||||
- Actors with physical access to the hardware
|
||||
- Actors with local network access to the system
|
||||
- Actors with access to the deployment system
|
||||
- Compromised internal systems under external control
|
||||
- Malicious end users of the system
|
||||
- Compromised end users of the system
|
||||
- Remote (external) compromise of the system
|
||||
|
||||
IPE does not mitigate threats arising from malicious but authorized
|
||||
developers (with access to a signing certificate), or compromised
|
||||
developer tools used by them (i.e. return-oriented programming attacks).
|
||||
Additionally, IPE draws hard security boundary between userspace and
|
||||
kernelspace. As a result, kernel-level exploits are considered outside
|
||||
the scope of IPE and mitigation is left to other mechanisms.
|
||||
|
||||
Policy
|
||||
------
|
||||
|
||||
IPE policy is a plain-text [#devdoc]_ policy composed of multiple statements
|
||||
over several lines. There is one required line, at the top of the
|
||||
policy, indicating the policy name, and the policy version, for
|
||||
instance::
|
||||
|
||||
policy_name=Ex_Policy policy_version=0.0.0
|
||||
|
||||
The policy name is a unique key identifying this policy in a human
|
||||
readable name. This is used to create nodes under securityfs as well as
|
||||
uniquely identify policies to deploy new policies vs update existing
|
||||
policies.
|
||||
|
||||
The policy version indicates the current version of the policy (NOT the
|
||||
policy syntax version). This is used to prevent rollback of policy to
|
||||
potentially insecure previous versions of the policy.
|
||||
|
||||
The next portion of IPE policy are rules. Rules are formed by key=value
|
||||
pairs, known as properties. IPE rules require two properties: ``action``,
|
||||
which determines what IPE does when it encounters a match against the
|
||||
rule, and ``op``, which determines when the rule should be evaluated.
|
||||
The ordering is significant, a rule must start with ``op``, and end with
|
||||
``action``. Thus, a minimal rule is::
|
||||
|
||||
op=EXECUTE action=ALLOW
|
||||
|
||||
This example will allow any execution. Additional properties are used to
|
||||
assess immutable security properties about the files being evaluated.
|
||||
These properties are intended to be descriptions of systems within the
|
||||
kernel that can provide a measure of integrity verification, such that IPE
|
||||
can determine the trust of the resource based on the value of the property.
|
||||
|
||||
Rules are evaluated top-to-bottom. As a result, any revocation rules,
|
||||
or denies should be placed early in the file to ensure that these rules
|
||||
are evaluated before a rule with ``action=ALLOW``.
|
||||
|
||||
IPE policy supports comments. The character '#' will function as a
|
||||
comment, ignoring all characters to the right of '#' until the newline.
|
||||
|
||||
The default behavior of IPE evaluations can also be expressed in policy,
|
||||
through the ``DEFAULT`` statement. This can be done at a global level,
|
||||
or a per-operation level::
|
||||
|
||||
# Global
|
||||
DEFAULT action=ALLOW
|
||||
|
||||
# Operation Specific
|
||||
DEFAULT op=EXECUTE action=ALLOW
|
||||
|
||||
A default must be set for all known operations in IPE. If you want to
|
||||
preserve older policies being compatible with newer kernels that can introduce
|
||||
new operations, set a global default of ``ALLOW``, then override the
|
||||
defaults on a per-operation basis (as above).
|
||||
|
||||
With configurable policy-based LSMs, there's several issues with
|
||||
enforcing the configurable policies at startup, around reading and
|
||||
parsing the policy:
|
||||
|
||||
1. The kernel *should* not read files from userspace, so directly reading
|
||||
the policy file is prohibited.
|
||||
2. The kernel command line has a character limit, and one kernel module
|
||||
should not reserve the entire character limit for its own
|
||||
configuration.
|
||||
3. There are various boot loaders in the kernel ecosystem, so handing
|
||||
off a memory block would be costly to maintain.
|
||||
|
||||
As a result, IPE has addressed this problem through a concept of a "boot
|
||||
policy". A boot policy is a minimal policy which is compiled into the
|
||||
kernel. This policy is intended to get the system to a state where
|
||||
userspace is set up and ready to receive commands, at which point a more
|
||||
complex policy can be deployed via securityfs. The boot policy can be
|
||||
specified via ``SECURITY_IPE_BOOT_POLICY`` config option, which accepts
|
||||
a path to a plain-text version of the IPE policy to apply. This policy
|
||||
will be compiled into the kernel. If not specified, IPE will be disabled
|
||||
until a policy is deployed and activated through securityfs.
|
||||
|
||||
Deploying Policies
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Policies can be deployed from userspace through securityfs. These policies
|
||||
are signed through the PKCS#7 message format to enforce some level of
|
||||
authorization of the policies (prohibiting an attacker from gaining
|
||||
unconstrained root, and deploying an "allow all" policy). These
|
||||
policies must be signed by a certificate that chains to the
|
||||
``SYSTEM_TRUSTED_KEYRING``. With openssl, the policy can be signed by::
|
||||
|
||||
openssl smime -sign \
|
||||
-in "$MY_POLICY" \
|
||||
-signer "$MY_CERTIFICATE" \
|
||||
-inkey "$MY_PRIVATE_KEY" \
|
||||
-noattr \
|
||||
-nodetach \
|
||||
-nosmimecap \
|
||||
-outform der \
|
||||
-out "$MY_POLICY.p7b"
|
||||
|
||||
Deploying the policies is done through securityfs, through the
|
||||
``new_policy`` node. To deploy a policy, simply cat the file into the
|
||||
securityfs node::
|
||||
|
||||
cat "$MY_POLICY.p7b" > /sys/kernel/security/ipe/new_policy
|
||||
|
||||
Upon success, this will create one subdirectory under
|
||||
``/sys/kernel/security/ipe/policies/``. The subdirectory will be the
|
||||
``policy_name`` field of the policy deployed, so for the example above,
|
||||
the directory will be ``/sys/kernel/security/ipe/policies/Ex_Policy``.
|
||||
Within this directory, there will be seven files: ``pkcs7``, ``policy``,
|
||||
``name``, ``version``, ``active``, ``update``, and ``delete``.
|
||||
|
||||
The ``pkcs7`` file is read-only. Reading it returns the raw PKCS#7 data
|
||||
that was provided to the kernel, representing the policy. If the policy being
|
||||
read is the boot policy, this will return ``ENOENT``, as it is not signed.
|
||||
|
||||
The ``policy`` file is read only. Reading it returns the PKCS#7 inner
|
||||
content of the policy, which will be the plain text policy.
|
||||
|
||||
The ``active`` file is used to set a policy as the currently active policy.
|
||||
This file is rw, and accepts a value of ``"1"`` to set the policy as active.
|
||||
Since only a single policy can be active at one time, all other policies
|
||||
will be marked inactive. The policy being marked active must have a policy
|
||||
version greater or equal to the currently-running version.
|
||||
|
||||
The ``update`` file is used to update a policy that is already present
|
||||
in the kernel. This file is write-only and accepts a PKCS#7 signed
|
||||
policy. Two checks will always be performed on this policy: First, the
|
||||
``policy_names`` must match with the updated version and the existing
|
||||
version. Second the updated policy must have a policy version greater than
|
||||
or equal to the currently-running version. This is to prevent rollback attacks.
|
||||
|
||||
The ``delete`` file is used to remove a policy that is no longer needed.
|
||||
This file is write-only and accepts a value of ``1`` to delete the policy.
|
||||
On deletion, the securityfs node representing the policy will be removed.
|
||||
However, delete the current active policy is not allowed and will return
|
||||
an operation not permitted error.
|
||||
|
||||
Similarly, writing to both ``update`` and ``new_policy`` could result in
|
||||
bad message(policy syntax error) or file exists error. The latter error happens
|
||||
when trying to deploy a policy with a ``policy_name`` while the kernel already
|
||||
has a deployed policy with the same ``policy_name``.
|
||||
|
||||
Deploying a policy will *not* cause IPE to start enforcing the policy. IPE will
|
||||
only enforce the policy marked active. Note that only one policy can be active
|
||||
at a time.
|
||||
|
||||
Once deployment is successful, the policy can be activated, by writing file
|
||||
``/sys/kernel/security/ipe/policies/$policy_name/active``.
|
||||
For example, the ``Ex_Policy`` can be activated by::
|
||||
|
||||
echo 1 > "/sys/kernel/security/ipe/policies/Ex_Policy/active"
|
||||
|
||||
From above point on, ``Ex_Policy`` is now the enforced policy on the
|
||||
system.
|
||||
|
||||
IPE also provides a way to delete policies. This can be done via the
|
||||
``delete`` securityfs node,
|
||||
``/sys/kernel/security/ipe/policies/$policy_name/delete``.
|
||||
Writing ``1`` to that file deletes the policy::
|
||||
|
||||
echo 1 > "/sys/kernel/security/ipe/policies/$policy_name/delete"
|
||||
|
||||
There is only one requirement to delete a policy: the policy being deleted
|
||||
must be inactive.
|
||||
|
||||
.. NOTE::
|
||||
|
||||
If a traditional MAC system is enabled (SELinux, apparmor, smack), all
|
||||
writes to ipe's securityfs nodes require ``CAP_MAC_ADMIN``.
|
||||
|
||||
Modes
|
||||
~~~~~
|
||||
|
||||
IPE supports two modes of operation: permissive (similar to SELinux's
|
||||
permissive mode) and enforced. In permissive mode, all events are
|
||||
checked and policy violations are logged, but the policy is not really
|
||||
enforced. This allows users to test policies before enforcing them.
|
||||
|
||||
The default mode is enforce, and can be changed via the kernel command
|
||||
line parameter ``ipe.enforce=(0|1)``, or the securityfs node
|
||||
``/sys/kernel/security/ipe/enforce``.
|
||||
|
||||
.. NOTE::
|
||||
|
||||
If a traditional MAC system is enabled (SELinux, apparmor, smack, etcetera),
|
||||
all writes to ipe's securityfs nodes require ``CAP_MAC_ADMIN``.
|
||||
|
||||
Audit Events
|
||||
~~~~~~~~~~~~
|
||||
|
||||
1420 AUDIT_IPE_ACCESS
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
Event Examples::
|
||||
|
||||
type=1420 audit(1653364370.067:61): ipe_op=EXECUTE ipe_hook=MMAP enforcing=1 pid=2241 comm="ld-linux.so" path="/deny/lib/libc.so.6" dev="sda2" ino=14549020 rule="DEFAULT action=DENY"
|
||||
type=1300 audit(1653364370.067:61): SYSCALL arch=c000003e syscall=9 success=no exit=-13 a0=7f1105a28000 a1=195000 a2=5 a3=812 items=0 ppid=2219 pid=2241 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts0 ses=2 comm="ld-linux.so" exe="/tmp/ipe-test/lib/ld-linux.so" subj=unconfined key=(null)
|
||||
type=1327 audit(1653364370.067:61): 707974686F6E3300746573742F6D61696E2E7079002D6E00
|
||||
|
||||
type=1420 audit(1653364735.161:64): ipe_op=EXECUTE ipe_hook=MMAP enforcing=1 pid=2472 comm="mmap_test" path=? dev=? ino=? rule="DEFAULT action=DENY"
|
||||
type=1300 audit(1653364735.161:64): SYSCALL arch=c000003e syscall=9 success=no exit=-13 a0=0 a1=1000 a2=4 a3=21 items=0 ppid=2219 pid=2472 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts0 ses=2 comm="mmap_test" exe="/root/overlake_test/upstream_test/vol_fsverity/bin/mmap_test" subj=unconfined key=(null)
|
||||
type=1327 audit(1653364735.161:64): 707974686F6E3300746573742F6D61696E2E7079002D6E00
|
||||
|
||||
This event indicates that IPE made an access control decision; the IPE
|
||||
specific record (1420) is always emitted in conjunction with a
|
||||
``AUDITSYSCALL`` record.
|
||||
|
||||
Determining whether IPE is in permissive or enforced mode can be derived
|
||||
from ``success`` property and exit code of the ``AUDITSYSCALL`` record.
|
||||
|
||||
|
||||
Field descriptions:
|
||||
|
||||
+-----------+------------+-----------+---------------------------------------------------------------------------------+
|
||||
| Field | Value Type | Optional? | Description of Value |
|
||||
+===========+============+===========+=================================================================================+
|
||||
| ipe_op | string | No | The IPE operation name associated with the log |
|
||||
+-----------+------------+-----------+---------------------------------------------------------------------------------+
|
||||
| ipe_hook | string | No | The name of the LSM hook that triggered the IPE event |
|
||||
+-----------+------------+-----------+---------------------------------------------------------------------------------+
|
||||
| enforcing | integer | No | The current IPE enforcing state 1 is in enforcing mode, 0 is in permissive mode |
|
||||
+-----------+------------+-----------+---------------------------------------------------------------------------------+
|
||||
| pid | integer | No | The pid of the process that triggered the IPE event. |
|
||||
+-----------+------------+-----------+---------------------------------------------------------------------------------+
|
||||
| comm | string | No | The command line program name of the process that triggered the IPE event |
|
||||
+-----------+------------+-----------+---------------------------------------------------------------------------------+
|
||||
| path | string | Yes | The absolute path to the evaluated file |
|
||||
+-----------+------------+-----------+---------------------------------------------------------------------------------+
|
||||
| ino | integer | Yes | The inode number of the evaluated file |
|
||||
+-----------+------------+-----------+---------------------------------------------------------------------------------+
|
||||
| dev | string | Yes | The device name of the evaluated file, e.g. vda |
|
||||
+-----------+------------+-----------+---------------------------------------------------------------------------------+
|
||||
| rule | string | No | The matched policy rule |
|
||||
+-----------+------------+-----------+---------------------------------------------------------------------------------+
|
||||
|
||||
1421 AUDIT_IPE_CONFIG_CHANGE
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Event Example::
|
||||
|
||||
type=1421 audit(1653425583.136:54): old_active_pol_name="Allow_All" old_active_pol_version=0.0.0 old_policy_digest=sha256:E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 new_active_pol_name="boot_verified" new_active_pol_version=0.0.0 new_policy_digest=sha256:820EEA5B40CA42B51F68962354BA083122A20BB846F26765076DD8EED7B8F4DB auid=4294967295 ses=4294967295 lsm=ipe res=1
|
||||
type=1300 audit(1653425583.136:54): SYSCALL arch=c000003e syscall=1 success=yes exit=2 a0=3 a1=5596fcae1fb0 a2=2 a3=2 items=0 ppid=184 pid=229 auid=4294967295 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts0 ses=4294967295 comm="python3" exe="/usr/bin/python3.10" key=(null)
|
||||
type=1327 audit(1653425583.136:54): PROCTITLE proctitle=707974686F6E3300746573742F6D61696E2E7079002D66002E2
|
||||
|
||||
This event indicates that IPE switched the active poliy from one to another
|
||||
along with the version and the hash digest of the two policies.
|
||||
Note IPE can only have one policy active at a time, all access decision
|
||||
evaluation is based on the current active policy.
|
||||
The normal procedure to deploy a new policy is loading the policy to deploy
|
||||
into the kernel first, then switch the active policy to it.
|
||||
|
||||
This record will always be emitted in conjunction with a ``AUDITSYSCALL`` record for the ``write`` syscall.
|
||||
|
||||
Field descriptions:
|
||||
|
||||
+------------------------+------------+-----------+---------------------------------------------------+
|
||||
| Field | Value Type | Optional? | Description of Value |
|
||||
+========================+============+===========+===================================================+
|
||||
| old_active_pol_name | string | Yes | The name of previous active policy |
|
||||
+------------------------+------------+-----------+---------------------------------------------------+
|
||||
| old_active_pol_version | string | Yes | The version of previous active policy |
|
||||
+------------------------+------------+-----------+---------------------------------------------------+
|
||||
| old_policy_digest | string | Yes | The hash of previous active policy |
|
||||
+------------------------+------------+-----------+---------------------------------------------------+
|
||||
| new_active_pol_name | string | No | The name of current active policy |
|
||||
+------------------------+------------+-----------+---------------------------------------------------+
|
||||
| new_active_pol_version | string | No | The version of current active policy |
|
||||
+------------------------+------------+-----------+---------------------------------------------------+
|
||||
| new_policy_digest | string | No | The hash of current active policy |
|
||||
+------------------------+------------+-----------+---------------------------------------------------+
|
||||
| auid | integer | No | The login user ID |
|
||||
+------------------------+------------+-----------+---------------------------------------------------+
|
||||
| ses | integer | No | The login session ID |
|
||||
+------------------------+------------+-----------+---------------------------------------------------+
|
||||
| lsm | string | No | The lsm name associated with the event |
|
||||
+------------------------+------------+-----------+---------------------------------------------------+
|
||||
| res | integer | No | The result of the audited operation(success/fail) |
|
||||
+------------------------+------------+-----------+---------------------------------------------------+
|
||||
|
||||
1422 AUDIT_IPE_POLICY_LOAD
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Event Example::
|
||||
|
||||
type=1422 audit(1653425529.927:53): policy_name="boot_verified" policy_version=0.0.0 policy_digest=sha256:820EEA5B40CA42B51F68962354BA083122A20BB846F26765076DD8EED7B8F4DB auid=4294967295 ses=4294967295 lsm=ipe res=1
|
||||
type=1300 audit(1653425529.927:53): arch=c000003e syscall=1 success=yes exit=2567 a0=3 a1=5596fcae1fb0 a2=a07 a3=2 items=0 ppid=184 pid=229 auid=4294967295 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts0 ses=4294967295 comm="python3" exe="/usr/bin/python3.10" key=(null)
|
||||
type=1327 audit(1653425529.927:53): PROCTITLE proctitle=707974686F6E3300746573742F6D61696E2E7079002D66002E2E
|
||||
|
||||
This record indicates a new policy has been loaded into the kernel with the policy name, policy version and policy hash.
|
||||
|
||||
This record will always be emitted in conjunction with a ``AUDITSYSCALL`` record for the ``write`` syscall.
|
||||
|
||||
Field descriptions:
|
||||
|
||||
+----------------+------------+-----------+---------------------------------------------------+
|
||||
| Field | Value Type | Optional? | Description of Value |
|
||||
+================+============+===========+===================================================+
|
||||
| policy_name | string | No | The policy_name |
|
||||
+----------------+------------+-----------+---------------------------------------------------+
|
||||
| policy_version | string | No | The policy_version |
|
||||
+----------------+------------+-----------+---------------------------------------------------+
|
||||
| policy_digest | string | No | The policy hash |
|
||||
+----------------+------------+-----------+---------------------------------------------------+
|
||||
| auid | integer | No | The login user ID |
|
||||
+----------------+------------+-----------+---------------------------------------------------+
|
||||
| ses | integer | No | The login session ID |
|
||||
+----------------+------------+-----------+---------------------------------------------------+
|
||||
| lsm | string | No | The lsm name associated with the event |
|
||||
+----------------+------------+-----------+---------------------------------------------------+
|
||||
| res | integer | No | The result of the audited operation(success/fail) |
|
||||
+----------------+------------+-----------+---------------------------------------------------+
|
||||
|
||||
|
||||
1404 AUDIT_MAC_STATUS
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Event Examples::
|
||||
|
||||
type=1404 audit(1653425689.008:55): enforcing=0 old_enforcing=1 auid=4294967295 ses=4294967295 enabled=1 old-enabled=1 lsm=ipe res=1
|
||||
type=1300 audit(1653425689.008:55): arch=c000003e syscall=1 success=yes exit=2 a0=1 a1=55c1065e5c60 a2=2 a3=0 items=0 ppid=405 pid=441 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=)
|
||||
type=1327 audit(1653425689.008:55): proctitle="-bash"
|
||||
|
||||
type=1404 audit(1653425689.008:55): enforcing=1 old_enforcing=0 auid=4294967295 ses=4294967295 enabled=1 old-enabled=1 lsm=ipe res=1
|
||||
type=1300 audit(1653425689.008:55): arch=c000003e syscall=1 success=yes exit=2 a0=1 a1=55c1065e5c60 a2=2 a3=0 items=0 ppid=405 pid=441 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=)
|
||||
type=1327 audit(1653425689.008:55): proctitle="-bash"
|
||||
|
||||
This record will always be emitted in conjunction with a ``AUDITSYSCALL`` record for the ``write`` syscall.
|
||||
|
||||
Field descriptions:
|
||||
|
||||
+---------------+------------+-----------+-------------------------------------------------------------------------------------------------+
|
||||
| Field | Value Type | Optional? | Description of Value |
|
||||
+===============+============+===========+=================================================================================================+
|
||||
| enforcing | integer | No | The enforcing state IPE is being switched to, 1 is in enforcing mode, 0 is in permissive mode |
|
||||
+---------------+------------+-----------+-------------------------------------------------------------------------------------------------+
|
||||
| old_enforcing | integer | No | The enforcing state IPE is being switched from, 1 is in enforcing mode, 0 is in permissive mode |
|
||||
+---------------+------------+-----------+-------------------------------------------------------------------------------------------------+
|
||||
| auid | integer | No | The login user ID |
|
||||
+---------------+------------+-----------+-------------------------------------------------------------------------------------------------+
|
||||
| ses | integer | No | The login session ID |
|
||||
+---------------+------------+-----------+-------------------------------------------------------------------------------------------------+
|
||||
| enabled | integer | No | The new TTY audit enabled setting |
|
||||
+---------------+------------+-----------+-------------------------------------------------------------------------------------------------+
|
||||
| old-enabled | integer | No | The old TTY audit enabled setting |
|
||||
+---------------+------------+-----------+-------------------------------------------------------------------------------------------------+
|
||||
| lsm | string | No | The lsm name associated with the event |
|
||||
+---------------+------------+-----------+-------------------------------------------------------------------------------------------------+
|
||||
| res | integer | No | The result of the audited operation(success/fail) |
|
||||
+---------------+------------+-----------+-------------------------------------------------------------------------------------------------+
|
||||
|
||||
|
||||
Success Auditing
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
IPE supports success auditing. When enabled, all events that pass IPE
|
||||
policy and are not blocked will emit an audit event. This is disabled by
|
||||
default, and can be enabled via the kernel command line
|
||||
``ipe.success_audit=(0|1)`` or
|
||||
``/sys/kernel/security/ipe/success_audit`` securityfs file.
|
||||
|
||||
This is *very* noisy, as IPE will check every userspace binary on the
|
||||
system, but is useful for debugging policies.
|
||||
|
||||
.. NOTE::
|
||||
|
||||
If a traditional MAC system is enabled (SELinux, apparmor, smack, etcetera),
|
||||
all writes to ipe's securityfs nodes require ``CAP_MAC_ADMIN``.
|
||||
|
||||
Properties
|
||||
----------
|
||||
|
||||
As explained above, IPE properties are ``key=value`` pairs expressed in IPE
|
||||
policy. Two properties are built-into the policy parser: 'op' and 'action'.
|
||||
The other properties are used to restrict immutable security properties
|
||||
about the files being evaluated. Currently those properties are:
|
||||
'``boot_verified``', '``dmverity_signature``', '``dmverity_roothash``',
|
||||
'``fsverity_signature``', '``fsverity_digest``'. A description of all
|
||||
properties supported by IPE are listed below:
|
||||
|
||||
op
|
||||
~~
|
||||
|
||||
Indicates the operation for a rule to apply to. Must be in every rule,
|
||||
as the first token. IPE supports the following operations:
|
||||
|
||||
``EXECUTE``
|
||||
|
||||
Pertains to any file attempting to be executed, or loaded as an
|
||||
executable.
|
||||
|
||||
``FIRMWARE``:
|
||||
|
||||
Pertains to firmware being loaded via the firmware_class interface.
|
||||
This covers both the preallocated buffer and the firmware file
|
||||
itself.
|
||||
|
||||
``KMODULE``:
|
||||
|
||||
Pertains to loading kernel modules via ``modprobe`` or ``insmod``.
|
||||
|
||||
``KEXEC_IMAGE``:
|
||||
|
||||
Pertains to kernel images loading via ``kexec``.
|
||||
|
||||
``KEXEC_INITRAMFS``
|
||||
|
||||
Pertains to initrd images loading via ``kexec --initrd``.
|
||||
|
||||
``POLICY``:
|
||||
|
||||
Controls loading policies via reading a kernel-space initiated read.
|
||||
|
||||
An example of such is loading IMA policies by writing the path
|
||||
to the policy file to ``$securityfs/ima/policy``
|
||||
|
||||
``X509_CERT``:
|
||||
|
||||
Controls loading IMA certificates through the Kconfigs,
|
||||
``CONFIG_IMA_X509_PATH`` and ``CONFIG_EVM_X509_PATH``.
|
||||
|
||||
action
|
||||
~~~~~~
|
||||
|
||||
Determines what IPE should do when a rule matches. Must be in every
|
||||
rule, as the final clause. Can be one of:
|
||||
|
||||
``ALLOW``:
|
||||
|
||||
If the rule matches, explicitly allow access to the resource to proceed
|
||||
without executing any more rules.
|
||||
|
||||
``DENY``:
|
||||
|
||||
If the rule matches, explicitly prohibit access to the resource to
|
||||
proceed without executing any more rules.
|
||||
|
||||
boot_verified
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
This property can be utilized for authorization of files from initramfs.
|
||||
The format of this property is::
|
||||
|
||||
boot_verified=(TRUE|FALSE)
|
||||
|
||||
|
||||
.. WARNING::
|
||||
|
||||
This property will trust files from initramfs(rootfs). It should
|
||||
only be used during early booting stage. Before mounting the real
|
||||
rootfs on top of the initramfs, initramfs script will recursively
|
||||
remove all files and directories on the initramfs. This is typically
|
||||
implemented by using switch_root(8) [#switch_root]_. Therefore the
|
||||
initramfs will be empty and not accessible after the real
|
||||
rootfs takes over. It is advised to switch to a different policy
|
||||
that doesn't rely on the property after this point.
|
||||
This ensures that the trust policies remain relevant and effective
|
||||
throughout the system's operation.
|
||||
|
||||
dmverity_roothash
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
This property can be utilized for authorization or revocation of
|
||||
specific dm-verity volumes, identified via their root hashes. It has a
|
||||
dependency on the DM_VERITY module. This property is controlled by
|
||||
the ``IPE_PROP_DM_VERITY`` config option, it will be automatically
|
||||
selected when ``SECURITY_IPE`` and ``DM_VERITY`` are all enabled.
|
||||
The format of this property is::
|
||||
|
||||
dmverity_roothash=DigestName:HexadecimalString
|
||||
|
||||
The supported DigestNames for dmverity_roothash are [#dmveritydigests]_
|
||||
|
||||
+ blake2b-512
|
||||
+ blake2s-256
|
||||
+ sha256
|
||||
+ sha384
|
||||
+ sha512
|
||||
+ sha3-224
|
||||
+ sha3-256
|
||||
+ sha3-384
|
||||
+ sha3-512
|
||||
+ sm3
|
||||
+ rmd160
|
||||
|
||||
dmverity_signature
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This property can be utilized for authorization of all dm-verity
|
||||
volumes that have a signed roothash that validated by a keyring
|
||||
specified by dm-verity's configuration, either the system trusted
|
||||
keyring, or the secondary keyring. It depends on
|
||||
``DM_VERITY_VERIFY_ROOTHASH_SIG`` config option and is controlled by
|
||||
the ``IPE_PROP_DM_VERITY_SIGNATURE`` config option, it will be automatically
|
||||
selected when ``SECURITY_IPE``, ``DM_VERITY`` and
|
||||
``DM_VERITY_VERIFY_ROOTHASH_SIG`` are all enabled.
|
||||
The format of this property is::
|
||||
|
||||
dmverity_signature=(TRUE|FALSE)
|
||||
|
||||
fsverity_digest
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
This property can be utilized for authorization of specific fsverity
|
||||
enabled files, identified via their fsverity digests.
|
||||
It depends on ``FS_VERITY`` config option and is controlled by
|
||||
the ``IPE_PROP_FS_VERITY`` config option, it will be automatically
|
||||
selected when ``SECURITY_IPE`` and ``FS_VERITY`` are all enabled.
|
||||
The format of this property is::
|
||||
|
||||
fsverity_digest=DigestName:HexadecimalString
|
||||
|
||||
The supported DigestNames for fsverity_digest are [#fsveritydigest]_
|
||||
|
||||
+ sha256
|
||||
+ sha512
|
||||
|
||||
fsverity_signature
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This property is used to authorize all fs-verity enabled files that have
|
||||
been verified by fs-verity's built-in signature mechanism. The signature
|
||||
verification relies on a key stored within the ".fs-verity" keyring. It
|
||||
depends on ``FS_VERITY_BUILTIN_SIGNATURES`` config option and
|
||||
it is controlled by the ``IPE_PROP_FS_VERITY`` config option,
|
||||
it will be automatically selected when ``SECURITY_IPE``, ``FS_VERITY``
|
||||
and ``FS_VERITY_BUILTIN_SIGNATURES`` are all enabled.
|
||||
The format of this property is::
|
||||
|
||||
fsverity_signature=(TRUE|FALSE)
|
||||
|
||||
Policy Examples
|
||||
---------------
|
||||
|
||||
Allow all
|
||||
~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
policy_name=Allow_All policy_version=0.0.0
|
||||
DEFAULT action=ALLOW
|
||||
|
||||
Allow only initramfs
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
policy_name=Allow_Initramfs policy_version=0.0.0
|
||||
DEFAULT action=DENY
|
||||
|
||||
op=EXECUTE boot_verified=TRUE action=ALLOW
|
||||
|
||||
Allow any signed and validated dm-verity volume and the initramfs
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
policy_name=Allow_Signed_DMV_And_Initramfs policy_version=0.0.0
|
||||
DEFAULT action=DENY
|
||||
|
||||
op=EXECUTE boot_verified=TRUE action=ALLOW
|
||||
op=EXECUTE dmverity_signature=TRUE action=ALLOW
|
||||
|
||||
Prohibit execution from a specific dm-verity volume
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
policy_name=Deny_DMV_By_Roothash policy_version=0.0.0
|
||||
DEFAULT action=DENY
|
||||
|
||||
op=EXECUTE dmverity_roothash=sha256:cd2c5bae7c6c579edaae4353049d58eb5f2e8be0244bf05345bc8e5ed257baff action=DENY
|
||||
|
||||
op=EXECUTE boot_verified=TRUE action=ALLOW
|
||||
op=EXECUTE dmverity_signature=TRUE action=ALLOW
|
||||
|
||||
Allow only a specific dm-verity volume
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
policy_name=Allow_DMV_By_Roothash policy_version=0.0.0
|
||||
DEFAULT action=DENY
|
||||
|
||||
op=EXECUTE dmverity_roothash=sha256:401fcec5944823ae12f62726e8184407a5fa9599783f030dec146938 action=ALLOW
|
||||
|
||||
Allow any fs-verity file with a valid built-in signature
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
policy_name=Allow_Signed_And_Validated_FSVerity policy_version=0.0.0
|
||||
DEFAULT action=DENY
|
||||
|
||||
op=EXECUTE fsverity_signature=TRUE action=ALLOW
|
||||
|
||||
Allow execution of a specific fs-verity file
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
policy_name=ALLOW_FSV_By_Digest policy_version=0.0.0
|
||||
DEFAULT action=DENY
|
||||
|
||||
op=EXECUTE fsverity_digest=sha256:fd88f2b8824e197f850bf4c5109bea5cf0ee38104f710843bb72da796ba5af9e action=ALLOW
|
||||
|
||||
Additional Information
|
||||
----------------------
|
||||
|
||||
- `Github Repository <https://github.com/microsoft/ipe>`_
|
||||
- :doc:`Developer and design docs for IPE </security/ipe>`
|
||||
|
||||
FAQ
|
||||
---
|
||||
|
||||
Q:
|
||||
What's the difference between other LSMs which provide a measure of
|
||||
trust-based access control?
|
||||
|
||||
A:
|
||||
|
||||
In general, there's two other LSMs that can provide similar functionality:
|
||||
IMA, and Loadpin.
|
||||
|
||||
IMA and IPE are functionally very similar. The significant difference between
|
||||
the two is the policy. [#devdoc]_
|
||||
|
||||
Loadpin and IPE differ fairly dramatically, as Loadpin only covers the IPE's
|
||||
kernel read operations, whereas IPE is capable of controlling execution
|
||||
on top of kernel read. The trust model is also different; Loadpin roots its
|
||||
trust in the initial super-block, whereas trust in IPE is stemmed from kernel
|
||||
itself (via ``SYSTEM_TRUSTED_KEYS``).
|
||||
|
||||
-----------
|
||||
|
||||
.. [#digest_cache_lsm] https://lore.kernel.org/lkml/20240415142436.2545003-1-roberto.sassu@huaweicloud.com/
|
||||
|
||||
.. [#interpreters] There is `some interest in solving this issue <https://lore.kernel.org/lkml/20220321161557.495388-1-mic@digikod.net/>`_.
|
||||
|
||||
.. [#devdoc] Please see :doc:`the design docs </security/ipe>` for more on
|
||||
this topic.
|
||||
|
||||
.. [#switch_root] https://man7.org/linux/man-pages/man8/switch_root.8.html
|
||||
|
||||
.. [#dmveritydigests] These hash algorithms are based on values accepted by
|
||||
the Linux crypto API; IPE does not impose any
|
||||
restrictions on the digest algorithm itself;
|
||||
thus, this list may be out of date.
|
||||
|
||||
.. [#fsveritydigest] These hash algorithms are based on values accepted by the
|
||||
kernel's fsverity support; IPE does not impose any
|
||||
restrictions on the digest algorithm itself;
|
||||
thus, this list may be out of date.
|
||||
@@ -2354,6 +2354,18 @@
|
||||
ipcmni_extend [KNL,EARLY] Extend the maximum number of unique System V
|
||||
IPC identifiers from 32,768 to 16,777,216.
|
||||
|
||||
ipe.enforce= [IPE]
|
||||
Format: <bool>
|
||||
Determine whether IPE starts in permissive (0) or
|
||||
enforce (1) mode. The default is enforce.
|
||||
|
||||
ipe.success_audit=
|
||||
[IPE]
|
||||
Format: <bool>
|
||||
Start IPE with success auditing enabled, emitting
|
||||
an audit event when a binary is allowed. The default
|
||||
is 0.
|
||||
|
||||
irqaffinity= [SMP] Set the default irq affinity mask
|
||||
The argument is a cpu list, as described above.
|
||||
|
||||
|
||||
@@ -86,6 +86,16 @@ authenticating fs-verity file hashes include:
|
||||
signature in their "security.ima" extended attribute, as controlled
|
||||
by the IMA policy. For more information, see the IMA documentation.
|
||||
|
||||
- Integrity Policy Enforcement (IPE). IPE supports enforcing access
|
||||
control decisions based on immutable security properties of files,
|
||||
including those protected by fs-verity's built-in signatures.
|
||||
"IPE policy" specifically allows for the authorization of fs-verity
|
||||
files using properties ``fsverity_digest`` for identifying
|
||||
files by their verity digest, and ``fsverity_signature`` to authorize
|
||||
files with a verified fs-verity's built-in signature. For
|
||||
details on configuring IPE policies and understanding its operational
|
||||
modes, please refer to :doc:`IPE admin guide </admin-guide/LSM/ipe>`.
|
||||
|
||||
- Trusted userspace code in combination with `Built-in signature
|
||||
verification`_. This approach should be used only with great care.
|
||||
|
||||
@@ -457,7 +467,11 @@ Enabling this option adds the following:
|
||||
On success, the ioctl persists the signature alongside the Merkle
|
||||
tree. Then, any time the file is opened, the kernel verifies the
|
||||
file's actual digest against this signature, using the certificates
|
||||
in the ".fs-verity" keyring.
|
||||
in the ".fs-verity" keyring. This verification happens as long as the
|
||||
file's signature exists, regardless of the state of the sysctl variable
|
||||
"fs.verity.require_signatures" described in the next item. The IPE LSM
|
||||
relies on this behavior to recognize and label fsverity files
|
||||
that contain a verified built-in fsverity signature.
|
||||
|
||||
3. A new sysctl "fs.verity.require_signatures" is made available.
|
||||
When set to 1, the kernel requires that all verity files have a
|
||||
@@ -481,7 +495,7 @@ be carefully considered before using them:
|
||||
|
||||
- Builtin signature verification does *not* make the kernel enforce
|
||||
that any files actually have fs-verity enabled. Thus, it is not a
|
||||
complete authentication policy. Currently, if it is used, the only
|
||||
complete authentication policy. Currently, if it is used, one
|
||||
way to complete the authentication policy is for trusted userspace
|
||||
code to explicitly check whether files have fs-verity enabled with a
|
||||
signature before they are accessed. (With
|
||||
@@ -490,6 +504,15 @@ be carefully considered before using them:
|
||||
could just store the signature alongside the file and verify it
|
||||
itself using a cryptographic library, instead of using this feature.
|
||||
|
||||
- Another approach is to utilize fs-verity builtin signature
|
||||
verification in conjunction with the IPE LSM, which supports defining
|
||||
a kernel-enforced, system-wide authentication policy that allows only
|
||||
files with a verified fs-verity builtin signature to perform certain
|
||||
operations, such as execution. Note that IPE doesn't require
|
||||
fs.verity.require_signatures=1.
|
||||
Please refer to :doc:`IPE admin guide </admin-guide/LSM/ipe>` for
|
||||
more details.
|
||||
|
||||
- A file's builtin signature can only be set at the same time that
|
||||
fs-verity is being enabled on the file. Changing or deleting the
|
||||
builtin signature later requires re-creating the file.
|
||||
|
||||
@@ -19,3 +19,4 @@ Security Documentation
|
||||
digsig
|
||||
landlock
|
||||
secrets/index
|
||||
ipe
|
||||
|
||||
@@ -0,0 +1,446 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
Integrity Policy Enforcement (IPE) - Kernel Documentation
|
||||
=========================================================
|
||||
|
||||
.. NOTE::
|
||||
|
||||
This is documentation targeted at developers, instead of administrators.
|
||||
If you're looking for documentation on the usage of IPE, please see
|
||||
:doc:`IPE admin guide </admin-guide/LSM/ipe>`.
|
||||
|
||||
Historical Motivation
|
||||
---------------------
|
||||
|
||||
The original issue that prompted IPE's implementation was the creation
|
||||
of a locked-down system. This system would be born-secure, and have
|
||||
strong integrity guarantees over both the executable code, and specific
|
||||
*data files* on the system, that were critical to its function. These
|
||||
specific data files would not be readable unless they passed integrity
|
||||
policy. A mandatory access control system would be present, and
|
||||
as a result, xattrs would have to be protected. This lead to a selection
|
||||
of what would provide the integrity claims. At the time, there were two
|
||||
main mechanisms considered that could guarantee integrity for the system
|
||||
with these requirements:
|
||||
|
||||
1. IMA + EVM Signatures
|
||||
2. DM-Verity
|
||||
|
||||
Both options were carefully considered, however the choice to use DM-Verity
|
||||
over IMA+EVM as the *integrity mechanism* in the original use case of IPE
|
||||
was due to three main reasons:
|
||||
|
||||
1. Protection of additional attack vectors:
|
||||
|
||||
* With IMA+EVM, without an encryption solution, the system is vulnerable
|
||||
to offline attack against the aforementioned specific data files.
|
||||
|
||||
Unlike executables, read operations (like those on the protected data
|
||||
files), cannot be enforced to be globally integrity verified. This means
|
||||
there must be some form of selector to determine whether a read should
|
||||
enforce the integrity policy, or it should not.
|
||||
|
||||
At the time, this was done with mandatory access control labels. An IMA
|
||||
policy would indicate what labels required integrity verification, which
|
||||
presented an issue: EVM would protect the label, but if an attacker could
|
||||
modify filesystem offline, the attacker could wipe all the xattrs -
|
||||
including the SELinux labels that would be used to determine whether the
|
||||
file should be subject to integrity policy.
|
||||
|
||||
With DM-Verity, as the xattrs are saved as part of the Merkel tree, if
|
||||
offline mount occurs against the filesystem protected by dm-verity, the
|
||||
checksum no longer matches and the file fails to be read.
|
||||
|
||||
* As userspace binaries are paged in Linux, dm-verity also offers the
|
||||
additional protection against a hostile block device. In such an attack,
|
||||
the block device reports the appropriate content for the IMA hash
|
||||
initially, passing the required integrity check. Then, on the page fault
|
||||
that accesses the real data, will report the attacker's payload. Since
|
||||
dm-verity will check the data when the page fault occurs (and the disk
|
||||
access), this attack is mitigated.
|
||||
|
||||
2. Performance:
|
||||
|
||||
* dm-verity provides integrity verification on demand as blocks are
|
||||
read versus requiring the entire file being read into memory for
|
||||
validation.
|
||||
|
||||
3. Simplicity of signing:
|
||||
|
||||
* No need for two signatures (IMA, then EVM): one signature covers
|
||||
an entire block device.
|
||||
* Signatures can be stored externally to the filesystem metadata.
|
||||
* The signature supports an x.509-based signing infrastructure.
|
||||
|
||||
The next step was to choose a *policy* to enforce the integrity mechanism.
|
||||
The minimum requirements for the policy were:
|
||||
|
||||
1. The policy itself must be integrity verified (preventing trivial
|
||||
attack against it).
|
||||
2. The policy itself must be resistant to rollback attacks.
|
||||
3. The policy enforcement must have a permissive-like mode.
|
||||
4. The policy must be able to be updated, in its entirety, without
|
||||
a reboot.
|
||||
5. Policy updates must be atomic.
|
||||
6. The policy must support *revocations* of previously authored
|
||||
components.
|
||||
7. The policy must be auditable, at any point-of-time.
|
||||
|
||||
IMA, as the only integrity policy mechanism at the time, was
|
||||
considered against these list of requirements, and did not fulfill
|
||||
all of the minimum requirements. Extending IMA to cover these
|
||||
requirements was considered, but ultimately discarded for a
|
||||
two reasons:
|
||||
|
||||
1. Regression risk; many of these changes would result in
|
||||
dramatic code changes to IMA, which is already present in the
|
||||
kernel, and therefore might impact users.
|
||||
|
||||
2. IMA was used in the system for measurement and attestation;
|
||||
separation of measurement policy from local integrity policy
|
||||
enforcement was considered favorable.
|
||||
|
||||
Due to these reasons, it was decided that a new LSM should be created,
|
||||
whose responsibility would be only the local integrity policy enforcement.
|
||||
|
||||
Role and Scope
|
||||
--------------
|
||||
|
||||
IPE, as its name implies, is fundamentally an integrity policy enforcement
|
||||
solution; IPE does not mandate how integrity is provided, but instead
|
||||
leaves that decision to the system administrator to set the security bar,
|
||||
via the mechanisms that they select that suit their individual needs.
|
||||
There are several different integrity solutions that provide a different
|
||||
level of security guarantees; and IPE allows sysadmins to express policy for
|
||||
theoretically all of them.
|
||||
|
||||
IPE does not have an inherent mechanism to ensure integrity on its own.
|
||||
Instead, there are more effective layers available for building systems that
|
||||
can guarantee integrity. It's important to note that the mechanism for proving
|
||||
integrity is independent of the policy for enforcing that integrity claim.
|
||||
|
||||
Therefore, IPE was designed around:
|
||||
|
||||
1. Easy integrations with integrity providers.
|
||||
2. Ease of use for platform administrators/sysadmins.
|
||||
|
||||
Design Rationale:
|
||||
-----------------
|
||||
|
||||
IPE was designed after evaluating existing integrity policy solutions
|
||||
in other operating systems and environments. In this survey of other
|
||||
implementations, there were a few pitfalls identified:
|
||||
|
||||
1. Policies were not readable by humans, usually requiring a binary
|
||||
intermediary format.
|
||||
2. A single, non-customizable action was implicitly taken as a default.
|
||||
3. Debugging the policy required manual steps to determine what rule was violated.
|
||||
4. Authoring a policy required an in-depth knowledge of the larger system,
|
||||
or operating system.
|
||||
|
||||
IPE attempts to avoid all of these pitfalls.
|
||||
|
||||
Policy
|
||||
~~~~~~
|
||||
|
||||
Plain Text
|
||||
^^^^^^^^^^
|
||||
|
||||
IPE's policy is plain-text. This introduces slightly larger policy files than
|
||||
other LSMs, but solves two major problems that occurs with some integrity policy
|
||||
solutions on other platforms.
|
||||
|
||||
The first issue is one of code maintenance and duplication. To author policies,
|
||||
the policy has to be some form of string representation (be it structured,
|
||||
through XML, JSON, YAML, etcetera), to allow the policy author to understand
|
||||
what is being written. In a hypothetical binary policy design, a serializer
|
||||
is necessary to write the policy from the human readable form, to the binary
|
||||
form, and a deserializer is needed to interpret the binary form into a data
|
||||
structure in the kernel.
|
||||
|
||||
Eventually, another deserializer will be needed to transform the binary from
|
||||
back into the human-readable form with as much information preserved. This is because a
|
||||
user of this access control system will have to keep a lookup table of a checksum
|
||||
and the original file itself to try to understand what policies have been deployed
|
||||
on this system and what policies have not. For a single user, this may be alright,
|
||||
as old policies can be discarded almost immediately after the update takes hold.
|
||||
For users that manage computer fleets in the thousands, if not hundreds of thousands,
|
||||
with multiple different operating systems, and multiple different operational needs,
|
||||
this quickly becomes an issue, as stale policies from years ago may be present,
|
||||
quickly resulting in the need to recover the policy or fund extensive infrastructure
|
||||
to track what each policy contains.
|
||||
|
||||
With now three separate serializer/deserializers, maintenance becomes costly. If the
|
||||
policy avoids the binary format, there is only one required serializer: from the
|
||||
human-readable form to the data structure in kernel, saving on code maintenance,
|
||||
and retaining operability.
|
||||
|
||||
The second issue with a binary format is one of transparency. As IPE controls
|
||||
access based on the trust of the system's resources, it's policy must also be
|
||||
trusted to be changed. This is done through signatures, resulting in needing
|
||||
signing as a process. Signing, as a process, is typically done with a
|
||||
high security bar, as anything signed can be used to attack integrity
|
||||
enforcement systems. It is also important that, when signing something, that
|
||||
the signer is aware of what they are signing. A binary policy can cause
|
||||
obfuscation of that fact; what signers see is an opaque binary blob. A
|
||||
plain-text policy, on the other hand, the signers see the actual policy
|
||||
submitted for signing.
|
||||
|
||||
Boot Policy
|
||||
~~~~~~~~~~~
|
||||
|
||||
IPE, if configured appropriately, is able to enforce a policy as soon as a
|
||||
kernel is booted and usermode starts. That implies some level of storage
|
||||
of the policy to apply the minute usermode starts. Generally, that storage
|
||||
can be handled in one of three ways:
|
||||
|
||||
1. The policy file(s) live on disk and the kernel loads the policy prior
|
||||
to an code path that would result in an enforcement decision.
|
||||
2. The policy file(s) are passed by the bootloader to the kernel, who
|
||||
parses the policy.
|
||||
3. There is a policy file that is compiled into the kernel that is
|
||||
parsed and enforced on initialization.
|
||||
|
||||
The first option has problems: the kernel reading files from userspace
|
||||
is typically discouraged and very uncommon in the kernel.
|
||||
|
||||
The second option also has problems: Linux supports a variety of bootloaders
|
||||
across its entire ecosystem - every bootloader would have to support this
|
||||
new methodology or there must be an independent source. It would likely
|
||||
result in more drastic changes to the kernel startup than necessary.
|
||||
|
||||
The third option is the best but it's important to be aware that the policy
|
||||
will take disk space against the kernel it's compiled in. It's important to
|
||||
keep this policy generalized enough that userspace can load a new, more
|
||||
complicated policy, but restrictive enough that it will not overauthorize
|
||||
and cause security issues.
|
||||
|
||||
The initramfs provides a way that this bootup path can be established. The
|
||||
kernel starts with a minimal policy, that trusts the initramfs only. Inside
|
||||
the initramfs, when the real rootfs is mounted, but not yet transferred to,
|
||||
it deploys and activates a policy that trusts the new root filesystem.
|
||||
This prevents overauthorization at any step, and keeps the kernel policy
|
||||
to a minimal size.
|
||||
|
||||
Startup
|
||||
^^^^^^^
|
||||
|
||||
Not every system, however starts with an initramfs, so the startup policy
|
||||
compiled into the kernel will need some flexibility to express how trust
|
||||
is established for the next phase of the bootup. To this end, if we just
|
||||
make the compiled-in policy a full IPE policy, it allows system builders
|
||||
to express the first stage bootup requirements appropriately.
|
||||
|
||||
Updatable, Rebootless Policy
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
As requirements change over time (vulnerabilities are found in previously
|
||||
trusted applications, keys roll, etcetera). Updating a kernel to change the
|
||||
meet those security goals is not always a suitable option, as updates are not
|
||||
always risk-free, and blocking a security update leaves systems vulnerable.
|
||||
This means IPE requires a policy that can be completely updated (allowing
|
||||
revocations of existing policy) from a source external to the kernel (allowing
|
||||
policies to be updated without updating the kernel).
|
||||
|
||||
Additionally, since the kernel is stateless between invocations, and reading
|
||||
policy files off the disk from kernel space is a bad idea(tm), then the
|
||||
policy updates have to be done rebootlessly.
|
||||
|
||||
To allow an update from an external source, it could be potentially malicious,
|
||||
so this policy needs to have a way to be identified as trusted. This is
|
||||
done via a signature chained to a trust source in the kernel. Arbitrarily,
|
||||
this is the ``SYSTEM_TRUSTED_KEYRING``, a keyring that is initially
|
||||
populated at kernel compile-time, as this matches the expectation that the
|
||||
author of the compiled-in policy described above is the same entity that can
|
||||
deploy policy updates.
|
||||
|
||||
Anti-Rollback / Anti-Replay
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Over time, vulnerabilities are found and trusted resources may not be
|
||||
trusted anymore. IPE's policy has no exception to this. There can be
|
||||
instances where a mistaken policy author deploys an insecure policy,
|
||||
before correcting it with a secure policy.
|
||||
|
||||
Assuming that as soon as the insecure policy is signed, and an attacker
|
||||
acquires the insecure policy, IPE needs a way to prevent rollback
|
||||
from the secure policy update to the insecure policy update.
|
||||
|
||||
Initially, IPE's policy can have a policy_version that states the
|
||||
minimum required version across all policies that can be active on
|
||||
the system. This will prevent rollback while the system is live.
|
||||
|
||||
.. WARNING::
|
||||
|
||||
However, since the kernel is stateless across boots, this policy
|
||||
version will be reset to 0.0.0 on the next boot. System builders
|
||||
need to be aware of this, and ensure the new secure policies are
|
||||
deployed ASAP after a boot to ensure that the window of
|
||||
opportunity is minimal for an attacker to deploy the insecure policy.
|
||||
|
||||
Implicit Actions:
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
The issue of implicit actions only becomes visible when you consider
|
||||
a mixed level of security bars across multiple operations in a system.
|
||||
For example, consider a system that has strong integrity guarantees
|
||||
over both the executable code, and specific *data files* on the system,
|
||||
that were critical to its function. In this system, three types of policies
|
||||
are possible:
|
||||
|
||||
1. A policy in which failure to match any rules in the policy results
|
||||
in the action being denied.
|
||||
2. A policy in which failure to match any rules in the policy results
|
||||
in the action being allowed.
|
||||
3. A policy in which the action taken when no rules are matched is
|
||||
specified by the policy author.
|
||||
|
||||
The first option could make a policy like this::
|
||||
|
||||
op=EXECUTE integrity_verified=YES action=ALLOW
|
||||
|
||||
In the example system, this works well for the executables, as all
|
||||
executables should have integrity guarantees, without exception. The
|
||||
issue becomes with the second requirement about specific data files.
|
||||
This would result in a policy like this (assuming each line is
|
||||
evaluated in order)::
|
||||
|
||||
op=EXECUTE integrity_verified=YES action=ALLOW
|
||||
|
||||
op=READ integrity_verified=NO label=critical_t action=DENY
|
||||
op=READ action=ALLOW
|
||||
|
||||
This is somewhat clear if you read the docs, understand the policy
|
||||
is executed in order and that the default is a denial; however, the
|
||||
last line effectively changes that default to an ALLOW. This is
|
||||
required, because in a realistic system, there are some unverified
|
||||
reads (imagine appending to a log file).
|
||||
|
||||
The second option, matching no rules results in an allow, is clearer
|
||||
for the specific data files::
|
||||
|
||||
op=READ integrity_verified=NO label=critical_t action=DENY
|
||||
|
||||
And, like the first option, falls short with the execution scenario,
|
||||
effectively needing to override the default::
|
||||
|
||||
op=EXECUTE integrity_verified=YES action=ALLOW
|
||||
op=EXECUTE action=DENY
|
||||
|
||||
op=READ integrity_verified=NO label=critical_t action=DENY
|
||||
|
||||
This leaves the third option. Instead of making users be clever
|
||||
and override the default with an empty rule, force the end-user
|
||||
to consider what the appropriate default should be for their
|
||||
scenario and explicitly state it::
|
||||
|
||||
DEFAULT op=EXECUTE action=DENY
|
||||
op=EXECUTE integrity_verified=YES action=ALLOW
|
||||
|
||||
DEFAULT op=READ action=ALLOW
|
||||
op=READ integrity_verified=NO label=critical_t action=DENY
|
||||
|
||||
Policy Debugging:
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
When developing a policy, it is useful to know what line of the policy
|
||||
is being violated to reduce debugging costs; narrowing the scope of the
|
||||
investigation to the exact line that resulted in the action. Some integrity
|
||||
policy systems do not provide this information, instead providing the
|
||||
information that was used in the evaluation. This then requires a correlation
|
||||
with the policy to evaluate what went wrong.
|
||||
|
||||
Instead, IPE just emits the rule that was matched. This limits the scope
|
||||
of the investigation to the exact policy line (in the case of a specific
|
||||
rule), or the section (in the case of a DEFAULT). This decreases iteration
|
||||
and investigation times when policy failures are observed while evaluating
|
||||
policies.
|
||||
|
||||
IPE's policy engine is also designed in a way that it makes it obvious to
|
||||
a human of how to investigate a policy failure. Each line is evaluated in
|
||||
the sequence that is written, so the algorithm is very simple to follow
|
||||
for humans to recreate the steps and could have caused the failure. In other
|
||||
surveyed systems, optimizations occur (sorting rules, for instance) when loading
|
||||
the policy. In those systems, it requires multiple steps to debug, and the
|
||||
algorithm may not always be clear to the end-user without reading the code first.
|
||||
|
||||
Simplified Policy:
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Finally, IPE's policy is designed for sysadmins, not kernel developers. Instead
|
||||
of covering individual LSM hooks (or syscalls), IPE covers operations. This means
|
||||
instead of sysadmins needing to know that the syscalls ``mmap``, ``mprotect``,
|
||||
``execve``, and ``uselib`` must have rules protecting them, they must simple know
|
||||
that they want to restrict code execution. This limits the amount of bypasses that
|
||||
could occur due to a lack of knowledge of the underlying system; whereas the
|
||||
maintainers of IPE, being kernel developers can make the correct choice to determine
|
||||
whether something maps to these operations, and under what conditions.
|
||||
|
||||
Implementation Notes
|
||||
--------------------
|
||||
|
||||
Anonymous Memory
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Anonymous memory isn't treated any differently from any other access in IPE.
|
||||
When anonymous memory is mapped with ``+X``, it still comes into the ``file_mmap``
|
||||
or ``file_mprotect`` hook, but with a ``NULL`` file object. This is submitted to
|
||||
the evaluation, like any other file. However, all current trust properties will
|
||||
evaluate to false, as they are all file-based and the operation is not
|
||||
associated with a file.
|
||||
|
||||
.. WARNING::
|
||||
|
||||
This also occurs with the ``kernel_load_data`` hook, when the kernel is
|
||||
loading data from a userspace buffer that is not backed by a file. In this
|
||||
scenario all current trust properties will also evaluate to false.
|
||||
|
||||
Securityfs Interface
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The per-policy securityfs tree is somewhat unique. For example, for
|
||||
a standard securityfs policy tree::
|
||||
|
||||
MyPolicy
|
||||
|- active
|
||||
|- delete
|
||||
|- name
|
||||
|- pkcs7
|
||||
|- policy
|
||||
|- update
|
||||
|- version
|
||||
|
||||
The policy is stored in the ``->i_private`` data of the MyPolicy inode.
|
||||
|
||||
Tests
|
||||
-----
|
||||
|
||||
IPE has KUnit Tests for the policy parser. Recommended kunitconfig::
|
||||
|
||||
CONFIG_KUNIT=y
|
||||
CONFIG_SECURITY=y
|
||||
CONFIG_SECURITYFS=y
|
||||
CONFIG_PKCS7_MESSAGE_PARSER=y
|
||||
CONFIG_SYSTEM_DATA_VERIFICATION=y
|
||||
CONFIG_FS_VERITY=y
|
||||
CONFIG_FS_VERITY_BUILTIN_SIGNATURES=y
|
||||
CONFIG_BLOCK=y
|
||||
CONFIG_MD=y
|
||||
CONFIG_BLK_DEV_DM=y
|
||||
CONFIG_DM_VERITY=y
|
||||
CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG=y
|
||||
CONFIG_NET=y
|
||||
CONFIG_AUDIT=y
|
||||
CONFIG_AUDITSYSCALL=y
|
||||
CONFIG_BLK_DEV_INITRD=y
|
||||
|
||||
CONFIG_SECURITY_IPE=y
|
||||
CONFIG_IPE_PROP_DM_VERITY=y
|
||||
CONFIG_IPE_PROP_DM_VERITY_SIGNATURE=y
|
||||
CONFIG_IPE_PROP_FS_VERITY=y
|
||||
CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG=y
|
||||
CONFIG_SECURITY_IPE_KUNIT_TEST=y
|
||||
|
||||
In addition, IPE has a python based integration
|
||||
`test suite <https://github.com/microsoft/ipe/tree/test-suite>`_ that
|
||||
can test both user interfaces and enforcement functionalities.
|
||||
+10
@@ -11173,6 +11173,16 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity.git
|
||||
F: security/integrity/
|
||||
F: security/integrity/ima/
|
||||
|
||||
INTEGRITY POLICY ENFORCEMENT (IPE)
|
||||
M: Fan Wu <wufan@linux.microsoft.com>
|
||||
L: linux-security-module@vger.kernel.org
|
||||
S: Supported
|
||||
T: git https://github.com/microsoft/ipe.git
|
||||
F: Documentation/admin-guide/LSM/ipe.rst
|
||||
F: Documentation/security/ipe.rst
|
||||
F: scripts/ipe/
|
||||
F: security/ipe/
|
||||
|
||||
INTEL 810/815 FRAMEBUFFER DRIVER
|
||||
M: Antonino Daplas <adaplas@gmail.com>
|
||||
L: linux-fbdev@vger.kernel.org
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <linux/pseudo_fs.h>
|
||||
#include <linux/uio.h>
|
||||
#include <linux/namei.h>
|
||||
#include <linux/security.h>
|
||||
#include <linux/part_stat.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/stat.h>
|
||||
@@ -324,6 +325,11 @@ static struct inode *bdev_alloc_inode(struct super_block *sb)
|
||||
if (!ei)
|
||||
return NULL;
|
||||
memset(&ei->bdev, 0, sizeof(ei->bdev));
|
||||
|
||||
if (security_bdev_alloc(&ei->bdev)) {
|
||||
kmem_cache_free(bdev_cachep, ei);
|
||||
return NULL;
|
||||
}
|
||||
return &ei->vfs_inode;
|
||||
}
|
||||
|
||||
@@ -333,6 +339,7 @@ static void bdev_free_inode(struct inode *inode)
|
||||
|
||||
free_percpu(bdev->bd_stats);
|
||||
kfree(bdev->bd_meta_info);
|
||||
security_bdev_free(bdev);
|
||||
|
||||
if (!bdev_is_partition(bdev)) {
|
||||
if (bdev->bd_disk && bdev->bd_disk->bdi)
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <linux/scatterlist.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/jump_label.h>
|
||||
#include <linux/security.h>
|
||||
|
||||
#define DM_MSG_PREFIX "verity"
|
||||
|
||||
@@ -930,6 +931,41 @@ static void verity_io_hints(struct dm_target *ti, struct queue_limits *limits)
|
||||
limits->dma_alignment = limits->logical_block_size - 1;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SECURITY
|
||||
|
||||
static int verity_init_sig(struct dm_verity *v, const void *sig,
|
||||
size_t sig_size)
|
||||
{
|
||||
v->sig_size = sig_size;
|
||||
|
||||
if (sig) {
|
||||
v->root_digest_sig = kmemdup(sig, v->sig_size, GFP_KERNEL);
|
||||
if (!v->root_digest_sig)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void verity_free_sig(struct dm_verity *v)
|
||||
{
|
||||
kfree(v->root_digest_sig);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static inline int verity_init_sig(struct dm_verity *v, const void *sig,
|
||||
size_t sig_size)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void verity_free_sig(struct dm_verity *v)
|
||||
{
|
||||
}
|
||||
|
||||
#endif /* CONFIG_SECURITY */
|
||||
|
||||
static void verity_dtr(struct dm_target *ti)
|
||||
{
|
||||
struct dm_verity *v = ti->private;
|
||||
@@ -949,6 +985,7 @@ static void verity_dtr(struct dm_target *ti)
|
||||
kfree(v->initial_hashstate);
|
||||
kfree(v->root_digest);
|
||||
kfree(v->zero_digest);
|
||||
verity_free_sig(v);
|
||||
|
||||
if (v->ahash_tfm) {
|
||||
static_branch_dec(&ahash_enabled);
|
||||
@@ -1418,6 +1455,13 @@ static int verity_ctr(struct dm_target *ti, unsigned int argc, char **argv)
|
||||
ti->error = "Root hash verification failed";
|
||||
goto bad;
|
||||
}
|
||||
|
||||
r = verity_init_sig(v, verify_args.sig, verify_args.sig_size);
|
||||
if (r < 0) {
|
||||
ti->error = "Cannot allocate root digest signature";
|
||||
goto bad;
|
||||
}
|
||||
|
||||
v->hash_per_block_bits =
|
||||
__fls((1 << v->hash_dev_block_bits) / v->digest_size);
|
||||
|
||||
@@ -1559,8 +1603,79 @@ int dm_verity_get_root_digest(struct dm_target *ti, u8 **root_digest, unsigned i
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SECURITY
|
||||
|
||||
#ifdef CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG
|
||||
|
||||
static int verity_security_set_signature(struct block_device *bdev,
|
||||
struct dm_verity *v)
|
||||
{
|
||||
/*
|
||||
* if the dm-verity target is unsigned, v->root_digest_sig will
|
||||
* be NULL, and the hook call is still required to let LSMs mark
|
||||
* the device as unsigned. This information is crucial for LSMs to
|
||||
* block operations such as execution on unsigned files
|
||||
*/
|
||||
return security_bdev_setintegrity(bdev,
|
||||
LSM_INT_DMVERITY_SIG_VALID,
|
||||
v->root_digest_sig,
|
||||
v->sig_size);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static inline int verity_security_set_signature(struct block_device *bdev,
|
||||
struct dm_verity *v)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG */
|
||||
|
||||
/*
|
||||
* Expose verity target's root hash and signature data to LSMs before resume.
|
||||
*
|
||||
* Returns 0 on success, or -ENOMEM if the system is out of memory.
|
||||
*/
|
||||
static int verity_preresume(struct dm_target *ti)
|
||||
{
|
||||
struct block_device *bdev;
|
||||
struct dm_verity_digest root_digest;
|
||||
struct dm_verity *v;
|
||||
int r;
|
||||
|
||||
v = ti->private;
|
||||
bdev = dm_disk(dm_table_get_md(ti->table))->part0;
|
||||
root_digest.digest = v->root_digest;
|
||||
root_digest.digest_len = v->digest_size;
|
||||
if (static_branch_unlikely(&ahash_enabled) && !v->shash_tfm)
|
||||
root_digest.alg = crypto_ahash_alg_name(v->ahash_tfm);
|
||||
else
|
||||
root_digest.alg = crypto_shash_alg_name(v->shash_tfm);
|
||||
|
||||
r = security_bdev_setintegrity(bdev, LSM_INT_DMVERITY_ROOTHASH, &root_digest,
|
||||
sizeof(root_digest));
|
||||
if (r)
|
||||
return r;
|
||||
|
||||
r = verity_security_set_signature(bdev, v);
|
||||
if (r)
|
||||
goto bad;
|
||||
|
||||
return 0;
|
||||
|
||||
bad:
|
||||
|
||||
security_bdev_setintegrity(bdev, LSM_INT_DMVERITY_ROOTHASH, NULL, 0);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_SECURITY */
|
||||
|
||||
static struct target_type verity_target = {
|
||||
.name = "verity",
|
||||
/* Note: the LSMs depend on the singleton and immutable features */
|
||||
.features = DM_TARGET_SINGLETON | DM_TARGET_IMMUTABLE,
|
||||
.version = {1, 10, 0},
|
||||
.module = THIS_MODULE,
|
||||
@@ -1571,6 +1686,9 @@ static struct target_type verity_target = {
|
||||
.prepare_ioctl = verity_prepare_ioctl,
|
||||
.iterate_devices = verity_iterate_devices,
|
||||
.io_hints = verity_io_hints,
|
||||
#ifdef CONFIG_SECURITY
|
||||
.preresume = verity_preresume,
|
||||
#endif /* CONFIG_SECURITY */
|
||||
};
|
||||
module_dm(verity);
|
||||
|
||||
|
||||
@@ -45,6 +45,10 @@ struct dm_verity {
|
||||
u8 *salt; /* salt: its size is salt_size */
|
||||
u8 *initial_hashstate; /* salted initial state, if shash_tfm is set */
|
||||
u8 *zero_digest; /* digest for a zero block */
|
||||
#ifdef CONFIG_SECURITY
|
||||
u8 *root_digest_sig; /* signature of the root digest */
|
||||
unsigned int sig_size; /* root digest signature size */
|
||||
#endif /* CONFIG_SECURITY */
|
||||
unsigned int salt_size;
|
||||
sector_t data_start; /* data offset in 512-byte sectors */
|
||||
sector_t hash_start; /* hash start in blocks */
|
||||
|
||||
+4
-10
@@ -125,8 +125,8 @@ void file_f_owner_release(struct file *file)
|
||||
}
|
||||
}
|
||||
|
||||
static void f_modown(struct file *filp, struct pid *pid, enum pid_type type,
|
||||
int force)
|
||||
void __f_setown(struct file *filp, struct pid *pid, enum pid_type type,
|
||||
int force)
|
||||
{
|
||||
struct fown_struct *f_owner;
|
||||
|
||||
@@ -142,19 +142,13 @@ static void f_modown(struct file *filp, struct pid *pid, enum pid_type type,
|
||||
|
||||
if (pid) {
|
||||
const struct cred *cred = current_cred();
|
||||
security_file_set_fowner(filp);
|
||||
f_owner->uid = cred->uid;
|
||||
f_owner->euid = cred->euid;
|
||||
}
|
||||
}
|
||||
write_unlock_irq(&f_owner->lock);
|
||||
}
|
||||
|
||||
void __f_setown(struct file *filp, struct pid *pid, enum pid_type type,
|
||||
int force)
|
||||
{
|
||||
security_file_set_fowner(filp);
|
||||
f_modown(filp, pid, type, force);
|
||||
}
|
||||
EXPORT_SYMBOL(__f_setown);
|
||||
|
||||
int f_setown(struct file *filp, int who, int force)
|
||||
@@ -196,7 +190,7 @@ EXPORT_SYMBOL(f_setown);
|
||||
|
||||
void f_delown(struct file *filp)
|
||||
{
|
||||
f_modown(filp, NULL, PIDTYPE_TGID, 1);
|
||||
__f_setown(filp, NULL, PIDTYPE_TGID, 1);
|
||||
}
|
||||
|
||||
pid_t f_getown(struct file *filp)
|
||||
|
||||
@@ -115,12 +115,12 @@ int ovl_copy_xattr(struct super_block *sb, const struct path *oldpath, struct de
|
||||
continue;
|
||||
|
||||
error = security_inode_copy_up_xattr(old, name);
|
||||
if (error < 0 && error != -EOPNOTSUPP)
|
||||
break;
|
||||
if (error == 1) {
|
||||
if (error == -ECANCELED) {
|
||||
error = 0;
|
||||
continue; /* Discard */
|
||||
}
|
||||
if (error < 0 && error != -EOPNOTSUPP)
|
||||
break;
|
||||
|
||||
if (is_posix_acl_xattr(name)) {
|
||||
error = ovl_copy_acl(OVL_FS(sb), oldpath, new, name);
|
||||
|
||||
+17
-1
@@ -17,6 +17,7 @@
|
||||
|
||||
#include <linux/cred.h>
|
||||
#include <linux/key.h>
|
||||
#include <linux/security.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/verification.h>
|
||||
|
||||
@@ -41,7 +42,11 @@ static struct key *fsverity_keyring;
|
||||
* @sig_size: size of signature in bytes, or 0 if no signature
|
||||
*
|
||||
* If the file includes a signature of its fs-verity file digest, verify it
|
||||
* against the certificates in the fs-verity keyring.
|
||||
* against the certificates in the fs-verity keyring. Note that signatures
|
||||
* are verified regardless of the state of the 'fsverity_require_signatures'
|
||||
* variable and the LSM subsystem relies on this behavior to help enforce
|
||||
* file integrity policies. Please discuss changes with the LSM list
|
||||
* (thank you!).
|
||||
*
|
||||
* Return: 0 on success (signature valid or not required); -errno on failure
|
||||
*/
|
||||
@@ -106,6 +111,17 @@ int fsverity_verify_signature(const struct fsverity_info *vi,
|
||||
return err;
|
||||
}
|
||||
|
||||
err = security_inode_setintegrity(inode,
|
||||
LSM_INT_FSVERITY_BUILTINSIG_VALID,
|
||||
signature,
|
||||
sig_size);
|
||||
|
||||
if (err) {
|
||||
fsverity_err(inode, "Error %d exposing file signature to LSMs",
|
||||
err);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
* that as _n.
|
||||
*/
|
||||
|
||||
/* This counts to 12. Any more, it will return 13th argument. */
|
||||
#define __COUNT_ARGS(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _n, X...) _n
|
||||
#define COUNT_ARGS(X...) __COUNT_ARGS(, ##X, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
|
||||
/* This counts to 15. Any more, it will return 16th argument. */
|
||||
#define __COUNT_ARGS(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _n, X...) _n
|
||||
#define COUNT_ARGS(X...) __COUNT_ARGS(, ##X, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
|
||||
|
||||
/* Concatenate two parameters, but allow them to be expanded beforehand. */
|
||||
#define __CONCAT(a, b) a ## b
|
||||
|
||||
@@ -71,6 +71,9 @@ struct block_device {
|
||||
|
||||
struct partition_meta_info *bd_meta_info;
|
||||
int bd_writers;
|
||||
#ifdef CONFIG_SECURITY
|
||||
void *bd_security;
|
||||
#endif
|
||||
/*
|
||||
* keep this out-of-line as it's both big and not needed in the fast
|
||||
* path
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
/*
|
||||
* Copyright (C) 2023 Google LLC.
|
||||
*/
|
||||
|
||||
#ifndef __LINUX_LSM_COUNT_H
|
||||
#define __LINUX_LSM_COUNT_H
|
||||
|
||||
#include <linux/args.h>
|
||||
|
||||
#ifdef CONFIG_SECURITY
|
||||
|
||||
/*
|
||||
* Macros to count the number of LSMs enabled in the kernel at compile time.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Capabilities is enabled when CONFIG_SECURITY is enabled.
|
||||
*/
|
||||
#if IS_ENABLED(CONFIG_SECURITY)
|
||||
#define CAPABILITIES_ENABLED 1,
|
||||
#else
|
||||
#define CAPABILITIES_ENABLED
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_SECURITY_SELINUX)
|
||||
#define SELINUX_ENABLED 1,
|
||||
#else
|
||||
#define SELINUX_ENABLED
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_SECURITY_SMACK)
|
||||
#define SMACK_ENABLED 1,
|
||||
#else
|
||||
#define SMACK_ENABLED
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_SECURITY_APPARMOR)
|
||||
#define APPARMOR_ENABLED 1,
|
||||
#else
|
||||
#define APPARMOR_ENABLED
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_SECURITY_TOMOYO)
|
||||
#define TOMOYO_ENABLED 1,
|
||||
#else
|
||||
#define TOMOYO_ENABLED
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_SECURITY_YAMA)
|
||||
#define YAMA_ENABLED 1,
|
||||
#else
|
||||
#define YAMA_ENABLED
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_SECURITY_LOADPIN)
|
||||
#define LOADPIN_ENABLED 1,
|
||||
#else
|
||||
#define LOADPIN_ENABLED
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_SECURITY_LOCKDOWN_LSM)
|
||||
#define LOCKDOWN_ENABLED 1,
|
||||
#else
|
||||
#define LOCKDOWN_ENABLED
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_SECURITY_SAFESETID)
|
||||
#define SAFESETID_ENABLED 1,
|
||||
#else
|
||||
#define SAFESETID_ENABLED
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_BPF_LSM)
|
||||
#define BPF_LSM_ENABLED 1,
|
||||
#else
|
||||
#define BPF_LSM_ENABLED
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_SECURITY_LANDLOCK)
|
||||
#define LANDLOCK_ENABLED 1,
|
||||
#else
|
||||
#define LANDLOCK_ENABLED
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_IMA)
|
||||
#define IMA_ENABLED 1,
|
||||
#else
|
||||
#define IMA_ENABLED
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_EVM)
|
||||
#define EVM_ENABLED 1,
|
||||
#else
|
||||
#define EVM_ENABLED
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_SECURITY_IPE)
|
||||
#define IPE_ENABLED 1,
|
||||
#else
|
||||
#define IPE_ENABLED
|
||||
#endif
|
||||
|
||||
/*
|
||||
* There is a trailing comma that we need to be accounted for. This is done by
|
||||
* using a skipped argument in __COUNT_LSMS
|
||||
*/
|
||||
#define __COUNT_LSMS(skipped_arg, args...) COUNT_ARGS(args...)
|
||||
#define COUNT_LSMS(args...) __COUNT_LSMS(args)
|
||||
|
||||
#define MAX_LSM_COUNT \
|
||||
COUNT_LSMS( \
|
||||
CAPABILITIES_ENABLED \
|
||||
SELINUX_ENABLED \
|
||||
SMACK_ENABLED \
|
||||
APPARMOR_ENABLED \
|
||||
TOMOYO_ENABLED \
|
||||
YAMA_ENABLED \
|
||||
LOADPIN_ENABLED \
|
||||
LOCKDOWN_ENABLED \
|
||||
SAFESETID_ENABLED \
|
||||
BPF_LSM_ENABLED \
|
||||
LANDLOCK_ENABLED \
|
||||
IMA_ENABLED \
|
||||
EVM_ENABLED \
|
||||
IPE_ENABLED)
|
||||
|
||||
#else
|
||||
|
||||
#define MAX_LSM_COUNT 0
|
||||
|
||||
#endif /* CONFIG_SECURITY */
|
||||
|
||||
#endif /* __LINUX_LSM_COUNT_H */
|
||||
@@ -48,7 +48,7 @@ LSM_HOOK(int, 0, quota_on, struct dentry *dentry)
|
||||
LSM_HOOK(int, 0, syslog, int type)
|
||||
LSM_HOOK(int, 0, settime, const struct timespec64 *ts,
|
||||
const struct timezone *tz)
|
||||
LSM_HOOK(int, 1, vm_enough_memory, struct mm_struct *mm, long pages)
|
||||
LSM_HOOK(int, 0, vm_enough_memory, struct mm_struct *mm, long pages)
|
||||
LSM_HOOK(int, 0, bprm_creds_for_exec, struct linux_binprm *bprm)
|
||||
LSM_HOOK(int, 0, bprm_creds_from_file, struct linux_binprm *bprm, const struct file *file)
|
||||
LSM_HOOK(int, 0, bprm_check_security, struct linux_binprm *bprm)
|
||||
@@ -114,6 +114,7 @@ LSM_HOOK(int, 0, path_notify, const struct path *path, u64 mask,
|
||||
unsigned int obj_type)
|
||||
LSM_HOOK(int, 0, inode_alloc_security, struct inode *inode)
|
||||
LSM_HOOK(void, LSM_RET_VOID, inode_free_security, struct inode *inode)
|
||||
LSM_HOOK(void, LSM_RET_VOID, inode_free_security_rcu, void *inode_security)
|
||||
LSM_HOOK(int, -EOPNOTSUPP, inode_init_security, struct inode *inode,
|
||||
struct inode *dir, const struct qstr *qstr, struct xattr *xattrs,
|
||||
int *xattr_count)
|
||||
@@ -179,6 +180,8 @@ LSM_HOOK(void, LSM_RET_VOID, inode_getsecid, struct inode *inode, u32 *secid)
|
||||
LSM_HOOK(int, 0, inode_copy_up, struct dentry *src, struct cred **new)
|
||||
LSM_HOOK(int, -EOPNOTSUPP, inode_copy_up_xattr, struct dentry *src,
|
||||
const char *name)
|
||||
LSM_HOOK(int, 0, inode_setintegrity, const struct inode *inode,
|
||||
enum lsm_integrity_type type, const void *value, size_t size)
|
||||
LSM_HOOK(int, 0, kernfs_init_security, struct kernfs_node *kn_dir,
|
||||
struct kernfs_node *kn)
|
||||
LSM_HOOK(int, 0, file_permission, struct file *file, int mask)
|
||||
@@ -353,8 +356,7 @@ LSM_HOOK(void, LSM_RET_VOID, secmark_refcount_inc, void)
|
||||
LSM_HOOK(void, LSM_RET_VOID, secmark_refcount_dec, void)
|
||||
LSM_HOOK(void, LSM_RET_VOID, req_classify_flow, const struct request_sock *req,
|
||||
struct flowi_common *flic)
|
||||
LSM_HOOK(int, 0, tun_dev_alloc_security, void **security)
|
||||
LSM_HOOK(void, LSM_RET_VOID, tun_dev_free_security, void *security)
|
||||
LSM_HOOK(int, 0, tun_dev_alloc_security, void *security)
|
||||
LSM_HOOK(int, 0, tun_dev_create, void)
|
||||
LSM_HOOK(int, 0, tun_dev_attach_queue, void *security)
|
||||
LSM_HOOK(int, 0, tun_dev_attach, struct sock *sk, void *security)
|
||||
@@ -374,8 +376,7 @@ LSM_HOOK(int, 0, mptcp_add_subflow, struct sock *sk, struct sock *ssk)
|
||||
LSM_HOOK(int, 0, ib_pkey_access, void *sec, u64 subnet_prefix, u16 pkey)
|
||||
LSM_HOOK(int, 0, ib_endport_manage_subnet, void *sec, const char *dev_name,
|
||||
u8 port_num)
|
||||
LSM_HOOK(int, 0, ib_alloc_security, void **sec)
|
||||
LSM_HOOK(void, LSM_RET_VOID, ib_free_security, void *sec)
|
||||
LSM_HOOK(int, 0, ib_alloc_security, void *sec)
|
||||
#endif /* CONFIG_SECURITY_INFINIBAND */
|
||||
|
||||
#ifdef CONFIG_SECURITY_NETWORK_XFRM
|
||||
@@ -403,7 +404,6 @@ LSM_HOOK(int, 0, xfrm_decode_session, struct sk_buff *skb, u32 *secid,
|
||||
#ifdef CONFIG_KEYS
|
||||
LSM_HOOK(int, 0, key_alloc, struct key *key, const struct cred *cred,
|
||||
unsigned long flags)
|
||||
LSM_HOOK(void, LSM_RET_VOID, key_free, struct key *key)
|
||||
LSM_HOOK(int, 0, key_permission, key_ref_t key_ref, const struct cred *cred,
|
||||
enum key_need_perm need_perm)
|
||||
LSM_HOOK(int, 0, key_getsecurity, struct key *key, char **buffer)
|
||||
@@ -442,7 +442,6 @@ LSM_HOOK(int, 0, locked_down, enum lockdown_reason what)
|
||||
#ifdef CONFIG_PERF_EVENTS
|
||||
LSM_HOOK(int, 0, perf_event_open, struct perf_event_attr *attr, int type)
|
||||
LSM_HOOK(int, 0, perf_event_alloc, struct perf_event *event)
|
||||
LSM_HOOK(void, LSM_RET_VOID, perf_event_free, struct perf_event *event)
|
||||
LSM_HOOK(int, 0, perf_event_read, struct perf_event *event)
|
||||
LSM_HOOK(int, 0, perf_event_write, struct perf_event *event)
|
||||
#endif /* CONFIG_PERF_EVENTS */
|
||||
@@ -452,3 +451,10 @@ LSM_HOOK(int, 0, uring_override_creds, const struct cred *new)
|
||||
LSM_HOOK(int, 0, uring_sqpoll, void)
|
||||
LSM_HOOK(int, 0, uring_cmd, struct io_uring_cmd *ioucmd)
|
||||
#endif /* CONFIG_IO_URING */
|
||||
|
||||
LSM_HOOK(void, LSM_RET_VOID, initramfs_populated, void)
|
||||
|
||||
LSM_HOOK(int, 0, bdev_alloc_security, struct block_device *bdev)
|
||||
LSM_HOOK(void, LSM_RET_VOID, bdev_free_security, struct block_device *bdev)
|
||||
LSM_HOOK(int, 0, bdev_setintegrity, struct block_device *bdev,
|
||||
enum lsm_integrity_type type, const void *value, size_t size)
|
||||
|
||||
+84
-45
@@ -30,19 +30,47 @@
|
||||
#include <linux/init.h>
|
||||
#include <linux/rculist.h>
|
||||
#include <linux/xattr.h>
|
||||
#include <linux/static_call.h>
|
||||
#include <linux/unroll.h>
|
||||
#include <linux/jump_label.h>
|
||||
#include <linux/lsm_count.h>
|
||||
|
||||
union security_list_options {
|
||||
#define LSM_HOOK(RET, DEFAULT, NAME, ...) RET (*NAME)(__VA_ARGS__);
|
||||
#include "lsm_hook_defs.h"
|
||||
#undef LSM_HOOK
|
||||
void *lsm_func_addr;
|
||||
};
|
||||
|
||||
struct security_hook_heads {
|
||||
#define LSM_HOOK(RET, DEFAULT, NAME, ...) struct hlist_head NAME;
|
||||
#include "lsm_hook_defs.h"
|
||||
#undef LSM_HOOK
|
||||
/*
|
||||
* @key: static call key as defined by STATIC_CALL_KEY
|
||||
* @trampoline: static call trampoline as defined by STATIC_CALL_TRAMP
|
||||
* @hl: The security_hook_list as initialized by the owning LSM.
|
||||
* @active: Enabled when the static call has an LSM hook associated.
|
||||
*/
|
||||
struct lsm_static_call {
|
||||
struct static_call_key *key;
|
||||
void *trampoline;
|
||||
struct security_hook_list *hl;
|
||||
/* this needs to be true or false based on what the key defaults to */
|
||||
struct static_key_false *active;
|
||||
} __randomize_layout;
|
||||
|
||||
/*
|
||||
* Table of the static calls for each LSM hook.
|
||||
* Once the LSMs are initialized, their callbacks will be copied to these
|
||||
* tables such that the calls are filled backwards (from last to first).
|
||||
* This way, we can jump directly to the first used static call, and execute
|
||||
* all of them after. This essentially makes the entry point
|
||||
* dynamic to adapt the number of static calls to the number of callbacks.
|
||||
*/
|
||||
struct lsm_static_calls_table {
|
||||
#define LSM_HOOK(RET, DEFAULT, NAME, ...) \
|
||||
struct lsm_static_call NAME[MAX_LSM_COUNT];
|
||||
#include <linux/lsm_hook_defs.h>
|
||||
#undef LSM_HOOK
|
||||
} __packed __randomize_layout;
|
||||
|
||||
/**
|
||||
* struct lsm_id - Identify a Linux Security Module.
|
||||
* @lsm: name of the LSM, must be approved by the LSM maintainers
|
||||
@@ -51,53 +79,45 @@ struct security_hook_heads {
|
||||
* Contains the information that identifies the LSM.
|
||||
*/
|
||||
struct lsm_id {
|
||||
const char *name;
|
||||
u64 id;
|
||||
const char *name;
|
||||
u64 id;
|
||||
};
|
||||
|
||||
/*
|
||||
* Security module hook list structure.
|
||||
* For use with generic list macros for common operations.
|
||||
*
|
||||
* struct security_hook_list - Contents of a cacheable, mappable object.
|
||||
* @scalls: The beginning of the array of static calls assigned to this hook.
|
||||
* @hook: The callback for the hook.
|
||||
* @lsm: The name of the lsm that owns this hook.
|
||||
*/
|
||||
struct security_hook_list {
|
||||
struct hlist_node list;
|
||||
struct hlist_head *head;
|
||||
union security_list_options hook;
|
||||
const struct lsm_id *lsmid;
|
||||
struct lsm_static_call *scalls;
|
||||
union security_list_options hook;
|
||||
const struct lsm_id *lsmid;
|
||||
} __randomize_layout;
|
||||
|
||||
/*
|
||||
* Security blob size or offset data.
|
||||
*/
|
||||
struct lsm_blob_sizes {
|
||||
int lbs_cred;
|
||||
int lbs_file;
|
||||
int lbs_inode;
|
||||
int lbs_superblock;
|
||||
int lbs_ipc;
|
||||
int lbs_msg_msg;
|
||||
int lbs_task;
|
||||
int lbs_xattr_count; /* number of xattr slots in new_xattrs array */
|
||||
int lbs_cred;
|
||||
int lbs_file;
|
||||
int lbs_ib;
|
||||
int lbs_inode;
|
||||
int lbs_sock;
|
||||
int lbs_superblock;
|
||||
int lbs_ipc;
|
||||
int lbs_key;
|
||||
int lbs_msg_msg;
|
||||
int lbs_perf_event;
|
||||
int lbs_task;
|
||||
int lbs_xattr_count; /* number of xattr slots in new_xattrs array */
|
||||
int lbs_tun_dev;
|
||||
int lbs_bdev;
|
||||
};
|
||||
|
||||
/**
|
||||
* lsm_get_xattr_slot - Return the next available slot and increment the index
|
||||
* @xattrs: array storing LSM-provided xattrs
|
||||
* @xattr_count: number of already stored xattrs (updated)
|
||||
*
|
||||
* Retrieve the first available slot in the @xattrs array to fill with an xattr,
|
||||
* and increment @xattr_count.
|
||||
*
|
||||
* Return: The slot to fill in @xattrs if non-NULL, NULL otherwise.
|
||||
*/
|
||||
static inline struct xattr *lsm_get_xattr_slot(struct xattr *xattrs,
|
||||
int *xattr_count)
|
||||
{
|
||||
if (unlikely(!xattrs))
|
||||
return NULL;
|
||||
return &xattrs[(*xattr_count)++];
|
||||
}
|
||||
|
||||
/*
|
||||
* LSM_RET_VOID is used as the default value in LSM_HOOK definitions for void
|
||||
* LSM hooks (in include/linux/lsm_hook_defs.h).
|
||||
@@ -110,11 +130,11 @@ static inline struct xattr *lsm_get_xattr_slot(struct xattr *xattrs,
|
||||
* care of the common case and reduces the amount of
|
||||
* text involved.
|
||||
*/
|
||||
#define LSM_HOOK_INIT(HEAD, HOOK) \
|
||||
{ .head = &security_hook_heads.HEAD, .hook = { .HEAD = HOOK } }
|
||||
|
||||
extern struct security_hook_heads security_hook_heads;
|
||||
extern char *lsm_names;
|
||||
#define LSM_HOOK_INIT(NAME, HOOK) \
|
||||
{ \
|
||||
.scalls = static_calls_table.NAME, \
|
||||
.hook = { .NAME = HOOK } \
|
||||
}
|
||||
|
||||
extern void security_add_hooks(struct security_hook_list *hooks, int count,
|
||||
const struct lsm_id *lsmid);
|
||||
@@ -137,9 +157,6 @@ struct lsm_info {
|
||||
struct lsm_blob_sizes *blobs; /* Optional: for blob sharing. */
|
||||
};
|
||||
|
||||
extern struct lsm_info __start_lsm_info[], __end_lsm_info[];
|
||||
extern struct lsm_info __start_early_lsm_info[], __end_early_lsm_info[];
|
||||
|
||||
#define DEFINE_LSM(lsm) \
|
||||
static struct lsm_info __lsm_##lsm \
|
||||
__used __section(".lsm_info.init") \
|
||||
@@ -150,6 +167,28 @@ extern struct lsm_info __start_early_lsm_info[], __end_early_lsm_info[];
|
||||
__used __section(".early_lsm_info.init") \
|
||||
__aligned(sizeof(unsigned long))
|
||||
|
||||
extern int lsm_inode_alloc(struct inode *inode);
|
||||
/* DO NOT tamper with these variables outside of the LSM framework */
|
||||
extern char *lsm_names;
|
||||
extern struct lsm_static_calls_table static_calls_table __ro_after_init;
|
||||
extern struct lsm_info __start_lsm_info[], __end_lsm_info[];
|
||||
extern struct lsm_info __start_early_lsm_info[], __end_early_lsm_info[];
|
||||
|
||||
/**
|
||||
* lsm_get_xattr_slot - Return the next available slot and increment the index
|
||||
* @xattrs: array storing LSM-provided xattrs
|
||||
* @xattr_count: number of already stored xattrs (updated)
|
||||
*
|
||||
* Retrieve the first available slot in the @xattrs array to fill with an xattr,
|
||||
* and increment @xattr_count.
|
||||
*
|
||||
* Return: The slot to fill in @xattrs if non-NULL, NULL otherwise.
|
||||
*/
|
||||
static inline struct xattr *lsm_get_xattr_slot(struct xattr *xattrs,
|
||||
int *xattr_count)
|
||||
{
|
||||
if (unlikely(!xattrs))
|
||||
return NULL;
|
||||
return &xattrs[(*xattr_count)++];
|
||||
}
|
||||
|
||||
#endif /* ! __LINUX_LSM_HOOKS_H */
|
||||
|
||||
@@ -83,6 +83,18 @@ enum lsm_event {
|
||||
LSM_POLICY_CHANGE,
|
||||
};
|
||||
|
||||
struct dm_verity_digest {
|
||||
const char *alg;
|
||||
const u8 *digest;
|
||||
size_t digest_len;
|
||||
};
|
||||
|
||||
enum lsm_integrity_type {
|
||||
LSM_INT_DMVERITY_SIG_VALID,
|
||||
LSM_INT_DMVERITY_ROOTHASH,
|
||||
LSM_INT_FSVERITY_BUILTINSIG_VALID,
|
||||
};
|
||||
|
||||
/*
|
||||
* These are reasons that can be passed to the security_locked_down()
|
||||
* LSM hook. Lockdown reasons that protect kernel integrity (ie, the
|
||||
@@ -399,6 +411,9 @@ int security_inode_listsecurity(struct inode *inode, char *buffer, size_t buffer
|
||||
void security_inode_getsecid(struct inode *inode, u32 *secid);
|
||||
int security_inode_copy_up(struct dentry *src, struct cred **new);
|
||||
int security_inode_copy_up_xattr(struct dentry *src, const char *name);
|
||||
int security_inode_setintegrity(const struct inode *inode,
|
||||
enum lsm_integrity_type type, const void *value,
|
||||
size_t size);
|
||||
int security_kernfs_init_security(struct kernfs_node *kn_dir,
|
||||
struct kernfs_node *kn);
|
||||
int security_file_permission(struct file *file, int mask);
|
||||
@@ -509,6 +524,11 @@ int security_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen);
|
||||
int security_locked_down(enum lockdown_reason what);
|
||||
int lsm_fill_user_ctx(struct lsm_ctx __user *uctx, u32 *uctx_len,
|
||||
void *val, size_t val_len, u64 id, u64 flags);
|
||||
int security_bdev_alloc(struct block_device *bdev);
|
||||
void security_bdev_free(struct block_device *bdev);
|
||||
int security_bdev_setintegrity(struct block_device *bdev,
|
||||
enum lsm_integrity_type type, const void *value,
|
||||
size_t size);
|
||||
#else /* CONFIG_SECURITY */
|
||||
|
||||
static inline int call_blocking_lsm_notifier(enum lsm_event event, void *data)
|
||||
@@ -634,7 +654,7 @@ static inline int security_settime64(const struct timespec64 *ts,
|
||||
|
||||
static inline int security_vm_enough_memory_mm(struct mm_struct *mm, long pages)
|
||||
{
|
||||
return __vm_enough_memory(mm, pages, cap_vm_enough_memory(mm, pages));
|
||||
return __vm_enough_memory(mm, pages, !cap_vm_enough_memory(mm, pages));
|
||||
}
|
||||
|
||||
static inline int security_bprm_creds_for_exec(struct linux_binprm *bprm)
|
||||
@@ -1010,6 +1030,13 @@ static inline int security_inode_copy_up(struct dentry *src, struct cred **new)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int security_inode_setintegrity(const struct inode *inode,
|
||||
enum lsm_integrity_type type,
|
||||
const void *value, size_t size)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int security_kernfs_init_security(struct kernfs_node *kn_dir,
|
||||
struct kernfs_node *kn)
|
||||
{
|
||||
@@ -1483,6 +1510,23 @@ static inline int lsm_fill_user_ctx(struct lsm_ctx __user *uctx,
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static inline int security_bdev_alloc(struct block_device *bdev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void security_bdev_free(struct block_device *bdev)
|
||||
{
|
||||
}
|
||||
|
||||
static inline int security_bdev_setintegrity(struct block_device *bdev,
|
||||
enum lsm_integrity_type type,
|
||||
const void *value, size_t size)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_SECURITY */
|
||||
|
||||
#if defined(CONFIG_SECURITY) && defined(CONFIG_WATCH_QUEUE)
|
||||
@@ -2090,6 +2134,7 @@ struct dentry *securityfs_create_symlink(const char *name,
|
||||
const char *target,
|
||||
const struct inode_operations *iops);
|
||||
extern void securityfs_remove(struct dentry *dentry);
|
||||
extern void securityfs_recursive_remove(struct dentry *dentry);
|
||||
|
||||
#else /* CONFIG_SECURITYFS */
|
||||
|
||||
@@ -2256,4 +2301,12 @@ static inline int security_uring_cmd(struct io_uring_cmd *ioucmd)
|
||||
#endif /* CONFIG_SECURITY */
|
||||
#endif /* CONFIG_IO_URING */
|
||||
|
||||
#ifdef CONFIG_SECURITY
|
||||
extern void security_initramfs_populated(void);
|
||||
#else
|
||||
static inline void security_initramfs_populated(void)
|
||||
{
|
||||
}
|
||||
#endif /* CONFIG_SECURITY */
|
||||
|
||||
#endif /* ! __LINUX_SECURITY_H */
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
/*
|
||||
* Copyright (C) 2023 Google LLC.
|
||||
*/
|
||||
|
||||
#ifndef __UNROLL_H
|
||||
#define __UNROLL_H
|
||||
|
||||
#include <linux/args.h>
|
||||
|
||||
#define UNROLL(N, MACRO, args...) CONCATENATE(__UNROLL_, N)(MACRO, args)
|
||||
|
||||
#define __UNROLL_0(MACRO, args...)
|
||||
#define __UNROLL_1(MACRO, args...) __UNROLL_0(MACRO, args) MACRO(0, args)
|
||||
#define __UNROLL_2(MACRO, args...) __UNROLL_1(MACRO, args) MACRO(1, args)
|
||||
#define __UNROLL_3(MACRO, args...) __UNROLL_2(MACRO, args) MACRO(2, args)
|
||||
#define __UNROLL_4(MACRO, args...) __UNROLL_3(MACRO, args) MACRO(3, args)
|
||||
#define __UNROLL_5(MACRO, args...) __UNROLL_4(MACRO, args) MACRO(4, args)
|
||||
#define __UNROLL_6(MACRO, args...) __UNROLL_5(MACRO, args) MACRO(5, args)
|
||||
#define __UNROLL_7(MACRO, args...) __UNROLL_6(MACRO, args) MACRO(6, args)
|
||||
#define __UNROLL_8(MACRO, args...) __UNROLL_7(MACRO, args) MACRO(7, args)
|
||||
#define __UNROLL_9(MACRO, args...) __UNROLL_8(MACRO, args) MACRO(8, args)
|
||||
#define __UNROLL_10(MACRO, args...) __UNROLL_9(MACRO, args) MACRO(9, args)
|
||||
#define __UNROLL_11(MACRO, args...) __UNROLL_10(MACRO, args) MACRO(10, args)
|
||||
#define __UNROLL_12(MACRO, args...) __UNROLL_11(MACRO, args) MACRO(11, args)
|
||||
#define __UNROLL_13(MACRO, args...) __UNROLL_12(MACRO, args) MACRO(12, args)
|
||||
#define __UNROLL_14(MACRO, args...) __UNROLL_13(MACRO, args) MACRO(13, args)
|
||||
#define __UNROLL_15(MACRO, args...) __UNROLL_14(MACRO, args) MACRO(14, args)
|
||||
#define __UNROLL_16(MACRO, args...) __UNROLL_15(MACRO, args) MACRO(15, args)
|
||||
#define __UNROLL_17(MACRO, args...) __UNROLL_16(MACRO, args) MACRO(16, args)
|
||||
#define __UNROLL_18(MACRO, args...) __UNROLL_17(MACRO, args) MACRO(17, args)
|
||||
#define __UNROLL_19(MACRO, args...) __UNROLL_18(MACRO, args) MACRO(18, args)
|
||||
#define __UNROLL_20(MACRO, args...) __UNROLL_19(MACRO, args) MACRO(19, args)
|
||||
|
||||
#endif /* __UNROLL_H */
|
||||
@@ -143,6 +143,9 @@
|
||||
#define AUDIT_MAC_UNLBL_STCDEL 1417 /* NetLabel: del a static label */
|
||||
#define AUDIT_MAC_CALIPSO_ADD 1418 /* NetLabel: add CALIPSO DOI entry */
|
||||
#define AUDIT_MAC_CALIPSO_DEL 1419 /* NetLabel: del CALIPSO DOI entry */
|
||||
#define AUDIT_IPE_ACCESS 1420 /* IPE denial or grant */
|
||||
#define AUDIT_IPE_CONFIG_CHANGE 1421 /* IPE config change */
|
||||
#define AUDIT_IPE_POLICY_LOAD 1422 /* IPE policy load */
|
||||
|
||||
#define AUDIT_FIRST_KERN_ANOM_MSG 1700
|
||||
#define AUDIT_LAST_KERN_ANOM_MSG 1799
|
||||
|
||||
@@ -64,6 +64,7 @@ struct lsm_ctx {
|
||||
#define LSM_ID_LANDLOCK 110
|
||||
#define LSM_ID_IMA 111
|
||||
#define LSM_ID_EVM 112
|
||||
#define LSM_ID_IPE 113
|
||||
|
||||
/*
|
||||
* LSM_ATTR_XXX definitions identify different LSM attributes
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <linux/namei.h>
|
||||
#include <linux/init_syscalls.h>
|
||||
#include <linux/umh.h>
|
||||
#include <linux/security.h>
|
||||
|
||||
#include "do_mounts.h"
|
||||
|
||||
@@ -712,6 +713,8 @@ static void __init do_populate_rootfs(void *unused, async_cookie_t cookie)
|
||||
}
|
||||
|
||||
done:
|
||||
security_initramfs_populated();
|
||||
|
||||
/*
|
||||
* If the initrd region is overlapped with crashkernel reserved region,
|
||||
* free only memory that is not part of crashkernel region.
|
||||
|
||||
+4
-2
@@ -922,8 +922,11 @@ void start_kernel(void)
|
||||
boot_cpu_init();
|
||||
page_address_init();
|
||||
pr_notice("%s", linux_banner);
|
||||
early_security_init();
|
||||
setup_arch(&command_line);
|
||||
/* Static keys and static calls are needed by LSMs */
|
||||
jump_label_init();
|
||||
static_call_init();
|
||||
early_security_init();
|
||||
setup_boot_config();
|
||||
setup_command_line(command_line);
|
||||
setup_nr_cpu_ids();
|
||||
@@ -934,7 +937,6 @@ void start_kernel(void)
|
||||
|
||||
pr_notice("Kernel command line: %s\n", saved_command_line);
|
||||
/* parameters may set static keys */
|
||||
jump_label_init();
|
||||
parse_early_param();
|
||||
after_dashes = parse_args("Booting kernel",
|
||||
static_command_line, __start___param,
|
||||
|
||||
@@ -55,6 +55,7 @@ targets += module.lds
|
||||
subdir-$(CONFIG_GCC_PLUGINS) += gcc-plugins
|
||||
subdir-$(CONFIG_MODVERSIONS) += genksyms
|
||||
subdir-$(CONFIG_SECURITY_SELINUX) += selinux
|
||||
subdir-$(CONFIG_SECURITY_IPE) += ipe
|
||||
|
||||
# Let clean descend into subdirs
|
||||
subdir- += basic dtc gdb kconfig mod
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
subdir-y := polgen
|
||||
@@ -0,0 +1,2 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
polgen
|
||||
@@ -0,0 +1,5 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
hostprogs-always-y := polgen
|
||||
HOST_EXTRACFLAGS += \
|
||||
-I$(srctree)/include \
|
||||
-I$(srctree)/include/uapi \
|
||||
@@ -0,0 +1,145 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
static void usage(const char *const name)
|
||||
{
|
||||
printf("Usage: %s OutputFile (PolicyFile)\n", name);
|
||||
exit(EINVAL);
|
||||
}
|
||||
|
||||
static int policy_to_buffer(const char *pathname, char **buffer, size_t *size)
|
||||
{
|
||||
size_t fsize;
|
||||
size_t read;
|
||||
char *lbuf;
|
||||
int rc = 0;
|
||||
FILE *fd;
|
||||
|
||||
fd = fopen(pathname, "r");
|
||||
if (!fd) {
|
||||
rc = errno;
|
||||
goto out;
|
||||
}
|
||||
|
||||
fseek(fd, 0, SEEK_END);
|
||||
fsize = ftell(fd);
|
||||
rewind(fd);
|
||||
|
||||
lbuf = malloc(fsize);
|
||||
if (!lbuf) {
|
||||
rc = ENOMEM;
|
||||
goto out_close;
|
||||
}
|
||||
|
||||
read = fread((void *)lbuf, sizeof(*lbuf), fsize, fd);
|
||||
if (read != fsize) {
|
||||
rc = -1;
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
*buffer = lbuf;
|
||||
*size = fsize;
|
||||
fclose(fd);
|
||||
|
||||
return rc;
|
||||
|
||||
out_free:
|
||||
free(lbuf);
|
||||
out_close:
|
||||
fclose(fd);
|
||||
out:
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int write_boot_policy(const char *pathname, const char *buf, size_t size)
|
||||
{
|
||||
int rc = 0;
|
||||
FILE *fd;
|
||||
size_t i;
|
||||
|
||||
fd = fopen(pathname, "w");
|
||||
if (!fd) {
|
||||
rc = errno;
|
||||
goto err;
|
||||
}
|
||||
|
||||
fprintf(fd, "/* This file is automatically generated.");
|
||||
fprintf(fd, " Do not edit. */\n");
|
||||
fprintf(fd, "#include <linux/stddef.h>\n");
|
||||
fprintf(fd, "\nextern const char *const ipe_boot_policy;\n\n");
|
||||
fprintf(fd, "const char *const ipe_boot_policy =\n");
|
||||
|
||||
if (!buf || size == 0) {
|
||||
fprintf(fd, "\tNULL;\n");
|
||||
fclose(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
fprintf(fd, "\t\"");
|
||||
|
||||
for (i = 0; i < size; ++i) {
|
||||
switch (buf[i]) {
|
||||
case '"':
|
||||
fprintf(fd, "\\\"");
|
||||
break;
|
||||
case '\'':
|
||||
fprintf(fd, "'");
|
||||
break;
|
||||
case '\n':
|
||||
fprintf(fd, "\\n\"\n\t\"");
|
||||
break;
|
||||
case '\\':
|
||||
fprintf(fd, "\\\\");
|
||||
break;
|
||||
case '\t':
|
||||
fprintf(fd, "\\t");
|
||||
break;
|
||||
case '\?':
|
||||
fprintf(fd, "\\?");
|
||||
break;
|
||||
default:
|
||||
fprintf(fd, "%c", buf[i]);
|
||||
}
|
||||
}
|
||||
fprintf(fd, "\";\n");
|
||||
fclose(fd);
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
if (fd)
|
||||
fclose(fd);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int main(int argc, const char *const argv[])
|
||||
{
|
||||
char *policy = NULL;
|
||||
size_t len = 0;
|
||||
int rc = 0;
|
||||
|
||||
if (argc < 2)
|
||||
usage(argv[0]);
|
||||
|
||||
if (argc > 2) {
|
||||
rc = policy_to_buffer(argv[2], &policy, &len);
|
||||
if (rc != 0)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
rc = write_boot_policy(argv[1], policy, len);
|
||||
cleanup:
|
||||
if (policy)
|
||||
free(policy);
|
||||
if (rc != 0)
|
||||
perror("An error occurred during policy conversion: ");
|
||||
return rc;
|
||||
}
|
||||
+6
-5
@@ -224,6 +224,7 @@ source "security/yama/Kconfig"
|
||||
source "security/safesetid/Kconfig"
|
||||
source "security/lockdown/Kconfig"
|
||||
source "security/landlock/Kconfig"
|
||||
source "security/ipe/Kconfig"
|
||||
|
||||
source "security/integrity/Kconfig"
|
||||
|
||||
@@ -263,11 +264,11 @@ endchoice
|
||||
|
||||
config LSM
|
||||
string "Ordered list of enabled LSMs"
|
||||
default "landlock,lockdown,yama,loadpin,safesetid,smack,selinux,tomoyo,apparmor,bpf" if DEFAULT_SECURITY_SMACK
|
||||
default "landlock,lockdown,yama,loadpin,safesetid,apparmor,selinux,smack,tomoyo,bpf" if DEFAULT_SECURITY_APPARMOR
|
||||
default "landlock,lockdown,yama,loadpin,safesetid,tomoyo,bpf" if DEFAULT_SECURITY_TOMOYO
|
||||
default "landlock,lockdown,yama,loadpin,safesetid,bpf" if DEFAULT_SECURITY_DAC
|
||||
default "landlock,lockdown,yama,loadpin,safesetid,selinux,smack,tomoyo,apparmor,bpf"
|
||||
default "landlock,lockdown,yama,loadpin,safesetid,smack,selinux,tomoyo,apparmor,ipe,bpf" if DEFAULT_SECURITY_SMACK
|
||||
default "landlock,lockdown,yama,loadpin,safesetid,apparmor,selinux,smack,tomoyo,ipe,bpf" if DEFAULT_SECURITY_APPARMOR
|
||||
default "landlock,lockdown,yama,loadpin,safesetid,tomoyo,ipe,bpf" if DEFAULT_SECURITY_TOMOYO
|
||||
default "landlock,lockdown,yama,loadpin,safesetid,ipe,bpf" if DEFAULT_SECURITY_DAC
|
||||
default "landlock,lockdown,yama,loadpin,safesetid,selinux,smack,tomoyo,apparmor,ipe,bpf"
|
||||
help
|
||||
A comma-separated list of LSMs, in initialization order.
|
||||
Any LSMs left off this list, except for those with order
|
||||
|
||||
@@ -25,6 +25,7 @@ obj-$(CONFIG_SECURITY_LOCKDOWN_LSM) += lockdown/
|
||||
obj-$(CONFIG_CGROUPS) += device_cgroup.o
|
||||
obj-$(CONFIG_BPF_LSM) += bpf/
|
||||
obj-$(CONFIG_SECURITY_LANDLOCK) += landlock/
|
||||
obj-$(CONFIG_SECURITY_IPE) += ipe/
|
||||
|
||||
# Object integrity file lists
|
||||
obj-$(CONFIG_INTEGRITY) += integrity/
|
||||
|
||||
@@ -51,10 +51,9 @@ struct aa_sk_ctx {
|
||||
struct aa_label *peer;
|
||||
};
|
||||
|
||||
#define SK_CTX(X) ((X)->sk_security)
|
||||
static inline struct aa_sk_ctx *aa_sock(const struct sock *sk)
|
||||
{
|
||||
return sk->sk_security;
|
||||
return sk->sk_security + apparmor_blob_sizes.lbs_sock;
|
||||
}
|
||||
|
||||
#define DEFINE_AUDIT_NET(NAME, OP, SK, F, T, P) \
|
||||
|
||||
+1
-16
@@ -1058,27 +1058,12 @@ static int apparmor_userns_create(const struct cred *cred)
|
||||
return error;
|
||||
}
|
||||
|
||||
static int apparmor_sk_alloc_security(struct sock *sk, int family, gfp_t flags)
|
||||
{
|
||||
struct aa_sk_ctx *ctx;
|
||||
|
||||
ctx = kzalloc(sizeof(*ctx), flags);
|
||||
if (!ctx)
|
||||
return -ENOMEM;
|
||||
|
||||
sk->sk_security = ctx;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void apparmor_sk_free_security(struct sock *sk)
|
||||
{
|
||||
struct aa_sk_ctx *ctx = aa_sock(sk);
|
||||
|
||||
sk->sk_security = NULL;
|
||||
aa_put_label(ctx->label);
|
||||
aa_put_label(ctx->peer);
|
||||
kfree(ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1433,6 +1418,7 @@ struct lsm_blob_sizes apparmor_blob_sizes __ro_after_init = {
|
||||
.lbs_cred = sizeof(struct aa_label *),
|
||||
.lbs_file = sizeof(struct aa_file_ctx),
|
||||
.lbs_task = sizeof(struct aa_task_ctx),
|
||||
.lbs_sock = sizeof(struct aa_sk_ctx),
|
||||
};
|
||||
|
||||
static const struct lsm_id apparmor_lsmid = {
|
||||
@@ -1478,7 +1464,6 @@ static struct security_hook_list apparmor_hooks[] __ro_after_init = {
|
||||
LSM_HOOK_INIT(getprocattr, apparmor_getprocattr),
|
||||
LSM_HOOK_INIT(setprocattr, apparmor_setprocattr),
|
||||
|
||||
LSM_HOOK_INIT(sk_alloc_security, apparmor_sk_alloc_security),
|
||||
LSM_HOOK_INIT(sk_free_security, apparmor_sk_free_security),
|
||||
LSM_HOOK_INIT(sk_clone_security, apparmor_sk_clone_security),
|
||||
|
||||
|
||||
@@ -151,7 +151,7 @@ static int aa_label_sk_perm(const struct cred *subj_cred,
|
||||
const char *op, u32 request,
|
||||
struct sock *sk)
|
||||
{
|
||||
struct aa_sk_ctx *ctx = SK_CTX(sk);
|
||||
struct aa_sk_ctx *ctx = aa_sock(sk);
|
||||
int error = 0;
|
||||
|
||||
AA_BUG(!label);
|
||||
|
||||
@@ -1396,17 +1396,12 @@ int cap_task_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
* Determine whether the allocation of a new virtual mapping by the current
|
||||
* task is permitted.
|
||||
*
|
||||
* Return: 1 if permission is granted, 0 if not.
|
||||
* Return: 0 if permission granted, negative error code if not.
|
||||
*/
|
||||
int cap_vm_enough_memory(struct mm_struct *mm, long pages)
|
||||
{
|
||||
int cap_sys_admin = 0;
|
||||
|
||||
if (cap_capable(current_cred(), &init_user_ns,
|
||||
CAP_SYS_ADMIN, CAP_OPT_NOAUDIT) == 0)
|
||||
cap_sys_admin = 1;
|
||||
|
||||
return cap_sys_admin;
|
||||
return cap_capable(current_cred(), &init_user_ns, CAP_SYS_ADMIN,
|
||||
CAP_OPT_NOAUDIT);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+26
-1
@@ -296,7 +296,7 @@ void securityfs_remove(struct dentry *dentry)
|
||||
{
|
||||
struct inode *dir;
|
||||
|
||||
if (!dentry || IS_ERR(dentry))
|
||||
if (IS_ERR_OR_NULL(dentry))
|
||||
return;
|
||||
|
||||
dir = d_inode(dentry->d_parent);
|
||||
@@ -313,6 +313,31 @@ void securityfs_remove(struct dentry *dentry)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(securityfs_remove);
|
||||
|
||||
static void remove_one(struct dentry *victim)
|
||||
{
|
||||
simple_release_fs(&mount, &mount_count);
|
||||
}
|
||||
|
||||
/**
|
||||
* securityfs_recursive_remove - recursively removes a file or directory
|
||||
*
|
||||
* @dentry: a pointer to a the dentry of the file or directory to be removed.
|
||||
*
|
||||
* This function recursively removes a file or directory in securityfs that was
|
||||
* previously created with a call to another securityfs function (like
|
||||
* securityfs_create_file() or variants thereof.)
|
||||
*/
|
||||
void securityfs_recursive_remove(struct dentry *dentry)
|
||||
{
|
||||
if (IS_ERR_OR_NULL(dentry))
|
||||
return;
|
||||
|
||||
simple_pin_fs(&fs_type, &mount, &mount_count);
|
||||
simple_recursive_removal(dentry, remove_one);
|
||||
simple_release_fs(&mount, &mount_count);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(securityfs_recursive_remove);
|
||||
|
||||
#ifdef CONFIG_SECURITY
|
||||
static struct dentry *lsm_dentry;
|
||||
static ssize_t lsm_read(struct file *filp, char __user *buf, size_t count,
|
||||
|
||||
@@ -1000,7 +1000,7 @@ static int evm_inode_copy_up_xattr(struct dentry *src, const char *name)
|
||||
case EVM_XATTR_HMAC:
|
||||
case EVM_IMA_XATTR_DIGSIG:
|
||||
default:
|
||||
rc = 1; /* discard */
|
||||
rc = -ECANCELED; /* discard */
|
||||
}
|
||||
|
||||
kfree(xattr_data);
|
||||
|
||||
@@ -223,7 +223,7 @@ static inline void ima_inode_set_iint(const struct inode *inode,
|
||||
|
||||
struct ima_iint_cache *ima_iint_find(struct inode *inode);
|
||||
struct ima_iint_cache *ima_inode_get(struct inode *inode);
|
||||
void ima_inode_free(struct inode *inode);
|
||||
void ima_inode_free_rcu(void *inode_security);
|
||||
void __init ima_iintcache_init(void);
|
||||
|
||||
extern const int read_idmap[];
|
||||
|
||||
@@ -109,22 +109,18 @@ struct ima_iint_cache *ima_inode_get(struct inode *inode)
|
||||
}
|
||||
|
||||
/**
|
||||
* ima_inode_free - Called on inode free
|
||||
* @inode: Pointer to the inode
|
||||
* ima_inode_free_rcu - Called to free an inode via a RCU callback
|
||||
* @inode_security: The inode->i_security pointer
|
||||
*
|
||||
* Free the iint associated with an inode.
|
||||
* Free the IMA data associated with an inode.
|
||||
*/
|
||||
void ima_inode_free(struct inode *inode)
|
||||
void ima_inode_free_rcu(void *inode_security)
|
||||
{
|
||||
struct ima_iint_cache *iint;
|
||||
struct ima_iint_cache **iint_p = inode_security + ima_blob_sizes.lbs_inode;
|
||||
|
||||
if (!IS_IMA(inode))
|
||||
return;
|
||||
|
||||
iint = ima_iint_find(inode);
|
||||
ima_inode_set_iint(inode, NULL);
|
||||
|
||||
ima_iint_free(iint);
|
||||
/* *iint_p should be NULL if !IS_IMA(inode) */
|
||||
if (*iint_p)
|
||||
ima_iint_free(*iint_p);
|
||||
}
|
||||
|
||||
static void ima_iint_init_once(void *foo)
|
||||
|
||||
@@ -1193,7 +1193,7 @@ static struct security_hook_list ima_hooks[] __ro_after_init = {
|
||||
#ifdef CONFIG_INTEGRITY_ASYMMETRIC_KEYS
|
||||
LSM_HOOK_INIT(kernel_module_request, ima_kernel_module_request),
|
||||
#endif
|
||||
LSM_HOOK_INIT(inode_free_security, ima_inode_free),
|
||||
LSM_HOOK_INIT(inode_free_security_rcu, ima_inode_free_rcu),
|
||||
};
|
||||
|
||||
static const struct lsm_id ima_lsmid = {
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
boot_policy.c
|
||||
@@ -0,0 +1,97 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# Integrity Policy Enforcement (IPE) configuration
|
||||
#
|
||||
|
||||
menuconfig SECURITY_IPE
|
||||
bool "Integrity Policy Enforcement (IPE)"
|
||||
depends on SECURITY && SECURITYFS && AUDIT && AUDITSYSCALL
|
||||
select PKCS7_MESSAGE_PARSER
|
||||
select SYSTEM_DATA_VERIFICATION
|
||||
select IPE_PROP_DM_VERITY if DM_VERITY
|
||||
select IPE_PROP_DM_VERITY_SIGNATURE if DM_VERITY && DM_VERITY_VERIFY_ROOTHASH_SIG
|
||||
select IPE_PROP_FS_VERITY if FS_VERITY
|
||||
select IPE_PROP_FS_VERITY_BUILTIN_SIG if FS_VERITY && FS_VERITY_BUILTIN_SIGNATURES
|
||||
help
|
||||
This option enables the Integrity Policy Enforcement LSM
|
||||
allowing users to define a policy to enforce a trust-based access
|
||||
control. A key feature of IPE is a customizable policy to allow
|
||||
admins to reconfigure trust requirements on the fly.
|
||||
|
||||
If unsure, answer N.
|
||||
|
||||
if SECURITY_IPE
|
||||
config IPE_BOOT_POLICY
|
||||
string "Integrity policy to apply on system startup"
|
||||
help
|
||||
This option specifies a filepath to an IPE policy that is compiled
|
||||
into the kernel. This policy will be enforced until a policy update
|
||||
is deployed via the $securityfs/ipe/policies/$policy_name/active
|
||||
interface.
|
||||
|
||||
If unsure, leave blank.
|
||||
|
||||
menu "IPE Trust Providers"
|
||||
|
||||
config IPE_PROP_DM_VERITY
|
||||
bool "Enable support for dm-verity based on root hash"
|
||||
depends on DM_VERITY
|
||||
help
|
||||
This option enables the 'dmverity_roothash' property within IPE
|
||||
policies. The property evaluates to TRUE when a file from a dm-verity
|
||||
volume is evaluated, and the volume's root hash matches the value
|
||||
supplied in the policy.
|
||||
|
||||
config IPE_PROP_DM_VERITY_SIGNATURE
|
||||
bool "Enable support for dm-verity based on root hash signature"
|
||||
depends on DM_VERITY && DM_VERITY_VERIFY_ROOTHASH_SIG
|
||||
help
|
||||
This option enables the 'dmverity_signature' property within IPE
|
||||
policies. The property evaluates to TRUE when a file from a dm-verity
|
||||
volume, which has been mounted with a valid signed root hash,
|
||||
is evaluated.
|
||||
|
||||
If unsure, answer Y.
|
||||
|
||||
config IPE_PROP_FS_VERITY
|
||||
bool "Enable support for fs-verity based on file digest"
|
||||
depends on FS_VERITY
|
||||
help
|
||||
This option enables the 'fsverity_digest' property within IPE
|
||||
policies. The property evaluates to TRUE when a file is fsverity
|
||||
enabled and its digest matches the supplied digest value in the
|
||||
policy.
|
||||
|
||||
if unsure, answer Y.
|
||||
|
||||
config IPE_PROP_FS_VERITY_BUILTIN_SIG
|
||||
bool "Enable support for fs-verity based on builtin signature"
|
||||
depends on FS_VERITY && FS_VERITY_BUILTIN_SIGNATURES
|
||||
help
|
||||
This option enables the 'fsverity_signature' property within IPE
|
||||
policies. The property evaluates to TRUE when a file is fsverity
|
||||
enabled and it has a valid builtin signature whose signing cert
|
||||
is in the .fs-verity keyring.
|
||||
|
||||
if unsure, answer Y.
|
||||
|
||||
endmenu
|
||||
|
||||
config SECURITY_IPE_KUNIT_TEST
|
||||
bool "Build KUnit tests for IPE" if !KUNIT_ALL_TESTS
|
||||
depends on KUNIT=y
|
||||
default KUNIT_ALL_TESTS
|
||||
help
|
||||
This builds the IPE KUnit tests.
|
||||
|
||||
KUnit tests run during boot and output the results to the debug log
|
||||
in TAP format (https://testanything.org/). Only useful for kernel devs
|
||||
running KUnit test harness and are not for inclusion into a
|
||||
production build.
|
||||
|
||||
For more information on KUnit and unit tests in general please refer
|
||||
to the KUnit documentation in Documentation/dev-tools/kunit/.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
endif
|
||||
@@ -0,0 +1,31 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# Makefile for building the IPE module as part of the kernel tree.
|
||||
#
|
||||
|
||||
quiet_cmd_polgen = IPE_POL $(2)
|
||||
cmd_polgen = scripts/ipe/polgen/polgen security/ipe/boot_policy.c $(2)
|
||||
|
||||
targets += boot_policy.c
|
||||
|
||||
$(obj)/boot_policy.c: scripts/ipe/polgen/polgen $(CONFIG_IPE_BOOT_POLICY) FORCE
|
||||
$(call if_changed,polgen,$(CONFIG_IPE_BOOT_POLICY))
|
||||
|
||||
obj-$(CONFIG_SECURITY_IPE) += \
|
||||
boot_policy.o \
|
||||
digest.o \
|
||||
eval.o \
|
||||
hooks.o \
|
||||
fs.o \
|
||||
ipe.o \
|
||||
policy.o \
|
||||
policy_fs.o \
|
||||
policy_parser.o \
|
||||
audit.o \
|
||||
|
||||
clean-files := boot_policy.c \
|
||||
|
||||
obj-$(CONFIG_SECURITY_IPE_KUNIT_TEST) += \
|
||||
policy_tests.o \
|
||||
@@ -0,0 +1,292 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/audit.h>
|
||||
#include <linux/types.h>
|
||||
#include <crypto/hash.h>
|
||||
|
||||
#include "ipe.h"
|
||||
#include "eval.h"
|
||||
#include "hooks.h"
|
||||
#include "policy.h"
|
||||
#include "audit.h"
|
||||
#include "digest.h"
|
||||
|
||||
#define ACTSTR(x) ((x) == IPE_ACTION_ALLOW ? "ALLOW" : "DENY")
|
||||
|
||||
#define IPE_AUDIT_HASH_ALG "sha256"
|
||||
|
||||
#define AUDIT_POLICY_LOAD_FMT "policy_name=\"%s\" policy_version=%hu.%hu.%hu "\
|
||||
"policy_digest=" IPE_AUDIT_HASH_ALG ":"
|
||||
#define AUDIT_OLD_ACTIVE_POLICY_FMT "old_active_pol_name=\"%s\" "\
|
||||
"old_active_pol_version=%hu.%hu.%hu "\
|
||||
"old_policy_digest=" IPE_AUDIT_HASH_ALG ":"
|
||||
#define AUDIT_OLD_ACTIVE_POLICY_NULL_FMT "old_active_pol_name=? "\
|
||||
"old_active_pol_version=? "\
|
||||
"old_policy_digest=?"
|
||||
#define AUDIT_NEW_ACTIVE_POLICY_FMT "new_active_pol_name=\"%s\" "\
|
||||
"new_active_pol_version=%hu.%hu.%hu "\
|
||||
"new_policy_digest=" IPE_AUDIT_HASH_ALG ":"
|
||||
|
||||
static const char *const audit_op_names[__IPE_OP_MAX + 1] = {
|
||||
"EXECUTE",
|
||||
"FIRMWARE",
|
||||
"KMODULE",
|
||||
"KEXEC_IMAGE",
|
||||
"KEXEC_INITRAMFS",
|
||||
"POLICY",
|
||||
"X509_CERT",
|
||||
"UNKNOWN",
|
||||
};
|
||||
|
||||
static const char *const audit_hook_names[__IPE_HOOK_MAX] = {
|
||||
"BPRM_CHECK",
|
||||
"MMAP",
|
||||
"MPROTECT",
|
||||
"KERNEL_READ",
|
||||
"KERNEL_LOAD",
|
||||
};
|
||||
|
||||
static const char *const audit_prop_names[__IPE_PROP_MAX] = {
|
||||
"boot_verified=FALSE",
|
||||
"boot_verified=TRUE",
|
||||
"dmverity_roothash=",
|
||||
"dmverity_signature=FALSE",
|
||||
"dmverity_signature=TRUE",
|
||||
"fsverity_digest=",
|
||||
"fsverity_signature=FALSE",
|
||||
"fsverity_signature=TRUE",
|
||||
};
|
||||
|
||||
/**
|
||||
* audit_dmv_roothash() - audit the roothash of a dmverity_roothash property.
|
||||
* @ab: Supplies a pointer to the audit_buffer to append to.
|
||||
* @rh: Supplies a pointer to the digest structure.
|
||||
*/
|
||||
static void audit_dmv_roothash(struct audit_buffer *ab, const void *rh)
|
||||
{
|
||||
audit_log_format(ab, "%s", audit_prop_names[IPE_PROP_DMV_ROOTHASH]);
|
||||
ipe_digest_audit(ab, rh);
|
||||
}
|
||||
|
||||
/**
|
||||
* audit_fsv_digest() - audit the digest of a fsverity_digest property.
|
||||
* @ab: Supplies a pointer to the audit_buffer to append to.
|
||||
* @d: Supplies a pointer to the digest structure.
|
||||
*/
|
||||
static void audit_fsv_digest(struct audit_buffer *ab, const void *d)
|
||||
{
|
||||
audit_log_format(ab, "%s", audit_prop_names[IPE_PROP_FSV_DIGEST]);
|
||||
ipe_digest_audit(ab, d);
|
||||
}
|
||||
|
||||
/**
|
||||
* audit_rule() - audit an IPE policy rule.
|
||||
* @ab: Supplies a pointer to the audit_buffer to append to.
|
||||
* @r: Supplies a pointer to the ipe_rule to approximate a string form for.
|
||||
*/
|
||||
static void audit_rule(struct audit_buffer *ab, const struct ipe_rule *r)
|
||||
{
|
||||
const struct ipe_prop *ptr;
|
||||
|
||||
audit_log_format(ab, " rule=\"op=%s ", audit_op_names[r->op]);
|
||||
|
||||
list_for_each_entry(ptr, &r->props, next) {
|
||||
switch (ptr->type) {
|
||||
case IPE_PROP_DMV_ROOTHASH:
|
||||
audit_dmv_roothash(ab, ptr->value);
|
||||
break;
|
||||
case IPE_PROP_FSV_DIGEST:
|
||||
audit_fsv_digest(ab, ptr->value);
|
||||
break;
|
||||
default:
|
||||
audit_log_format(ab, "%s", audit_prop_names[ptr->type]);
|
||||
break;
|
||||
}
|
||||
|
||||
audit_log_format(ab, " ");
|
||||
}
|
||||
|
||||
audit_log_format(ab, "action=%s\"", ACTSTR(r->action));
|
||||
}
|
||||
|
||||
/**
|
||||
* ipe_audit_match() - Audit a rule match in a policy evaluation.
|
||||
* @ctx: Supplies a pointer to the evaluation context that was used in the
|
||||
* evaluation.
|
||||
* @match_type: Supplies the scope of the match: rule, operation default,
|
||||
* global default.
|
||||
* @act: Supplies the IPE's evaluation decision, deny or allow.
|
||||
* @r: Supplies a pointer to the rule that was matched, if possible.
|
||||
*/
|
||||
void ipe_audit_match(const struct ipe_eval_ctx *const ctx,
|
||||
enum ipe_match match_type,
|
||||
enum ipe_action_type act, const struct ipe_rule *const r)
|
||||
{
|
||||
const char *op = audit_op_names[ctx->op];
|
||||
char comm[sizeof(current->comm)];
|
||||
struct audit_buffer *ab;
|
||||
struct inode *inode;
|
||||
|
||||
if (act != IPE_ACTION_DENY && !READ_ONCE(success_audit))
|
||||
return;
|
||||
|
||||
ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN,
|
||||
AUDIT_IPE_ACCESS);
|
||||
if (!ab)
|
||||
return;
|
||||
|
||||
audit_log_format(ab, "ipe_op=%s ipe_hook=%s enforcing=%d pid=%d comm=",
|
||||
op, audit_hook_names[ctx->hook], READ_ONCE(enforce),
|
||||
task_tgid_nr(current));
|
||||
audit_log_untrustedstring(ab, get_task_comm(comm, current));
|
||||
|
||||
if (ctx->file) {
|
||||
audit_log_d_path(ab, " path=", &ctx->file->f_path);
|
||||
inode = file_inode(ctx->file);
|
||||
if (inode) {
|
||||
audit_log_format(ab, " dev=");
|
||||
audit_log_untrustedstring(ab, inode->i_sb->s_id);
|
||||
audit_log_format(ab, " ino=%lu", inode->i_ino);
|
||||
} else {
|
||||
audit_log_format(ab, " dev=? ino=?");
|
||||
}
|
||||
} else {
|
||||
audit_log_format(ab, " path=? dev=? ino=?");
|
||||
}
|
||||
|
||||
if (match_type == IPE_MATCH_RULE)
|
||||
audit_rule(ab, r);
|
||||
else if (match_type == IPE_MATCH_TABLE)
|
||||
audit_log_format(ab, " rule=\"DEFAULT op=%s action=%s\"", op,
|
||||
ACTSTR(act));
|
||||
else
|
||||
audit_log_format(ab, " rule=\"DEFAULT action=%s\"",
|
||||
ACTSTR(act));
|
||||
|
||||
audit_log_end(ab);
|
||||
}
|
||||
|
||||
/**
|
||||
* audit_policy() - Audit a policy's name, version and thumbprint to @ab.
|
||||
* @ab: Supplies a pointer to the audit buffer to append to.
|
||||
* @audit_format: Supplies a pointer to the audit format string
|
||||
* @p: Supplies a pointer to the policy to audit.
|
||||
*/
|
||||
static void audit_policy(struct audit_buffer *ab,
|
||||
const char *audit_format,
|
||||
const struct ipe_policy *const p)
|
||||
{
|
||||
SHASH_DESC_ON_STACK(desc, tfm);
|
||||
struct crypto_shash *tfm;
|
||||
u8 *digest = NULL;
|
||||
|
||||
tfm = crypto_alloc_shash(IPE_AUDIT_HASH_ALG, 0, 0);
|
||||
if (IS_ERR(tfm))
|
||||
return;
|
||||
|
||||
desc->tfm = tfm;
|
||||
|
||||
digest = kzalloc(crypto_shash_digestsize(tfm), GFP_KERNEL);
|
||||
if (!digest)
|
||||
goto out;
|
||||
|
||||
if (crypto_shash_init(desc))
|
||||
goto out;
|
||||
|
||||
if (crypto_shash_update(desc, p->pkcs7, p->pkcs7len))
|
||||
goto out;
|
||||
|
||||
if (crypto_shash_final(desc, digest))
|
||||
goto out;
|
||||
|
||||
audit_log_format(ab, audit_format, p->parsed->name,
|
||||
p->parsed->version.major, p->parsed->version.minor,
|
||||
p->parsed->version.rev);
|
||||
audit_log_n_hex(ab, digest, crypto_shash_digestsize(tfm));
|
||||
|
||||
out:
|
||||
kfree(digest);
|
||||
crypto_free_shash(tfm);
|
||||
}
|
||||
|
||||
/**
|
||||
* ipe_audit_policy_activation() - Audit a policy being activated.
|
||||
* @op: Supplies a pointer to the previously activated policy to audit.
|
||||
* @np: Supplies a pointer to the newly activated policy to audit.
|
||||
*/
|
||||
void ipe_audit_policy_activation(const struct ipe_policy *const op,
|
||||
const struct ipe_policy *const np)
|
||||
{
|
||||
struct audit_buffer *ab;
|
||||
|
||||
ab = audit_log_start(audit_context(), GFP_KERNEL,
|
||||
AUDIT_IPE_CONFIG_CHANGE);
|
||||
if (!ab)
|
||||
return;
|
||||
|
||||
if (op) {
|
||||
audit_policy(ab, AUDIT_OLD_ACTIVE_POLICY_FMT, op);
|
||||
audit_log_format(ab, " ");
|
||||
} else {
|
||||
/*
|
||||
* old active policy can be NULL if there is no kernel
|
||||
* built-in policy
|
||||
*/
|
||||
audit_log_format(ab, AUDIT_OLD_ACTIVE_POLICY_NULL_FMT);
|
||||
audit_log_format(ab, " ");
|
||||
}
|
||||
audit_policy(ab, AUDIT_NEW_ACTIVE_POLICY_FMT, np);
|
||||
audit_log_format(ab, " auid=%u ses=%u lsm=ipe res=1",
|
||||
from_kuid(&init_user_ns, audit_get_loginuid(current)),
|
||||
audit_get_sessionid(current));
|
||||
|
||||
audit_log_end(ab);
|
||||
}
|
||||
|
||||
/**
|
||||
* ipe_audit_policy_load() - Audit a policy being loaded into the kernel.
|
||||
* @p: Supplies a pointer to the policy to audit.
|
||||
*/
|
||||
void ipe_audit_policy_load(const struct ipe_policy *const p)
|
||||
{
|
||||
struct audit_buffer *ab;
|
||||
|
||||
ab = audit_log_start(audit_context(), GFP_KERNEL,
|
||||
AUDIT_IPE_POLICY_LOAD);
|
||||
if (!ab)
|
||||
return;
|
||||
|
||||
audit_policy(ab, AUDIT_POLICY_LOAD_FMT, p);
|
||||
audit_log_format(ab, " auid=%u ses=%u lsm=ipe res=1",
|
||||
from_kuid(&init_user_ns, audit_get_loginuid(current)),
|
||||
audit_get_sessionid(current));
|
||||
|
||||
audit_log_end(ab);
|
||||
}
|
||||
|
||||
/**
|
||||
* ipe_audit_enforce() - Audit a change in IPE's enforcement state.
|
||||
* @new_enforce: The new value enforce to be set.
|
||||
* @old_enforce: The old value currently in enforce.
|
||||
*/
|
||||
void ipe_audit_enforce(bool new_enforce, bool old_enforce)
|
||||
{
|
||||
struct audit_buffer *ab;
|
||||
|
||||
ab = audit_log_start(audit_context(), GFP_KERNEL, AUDIT_MAC_STATUS);
|
||||
if (!ab)
|
||||
return;
|
||||
|
||||
audit_log(audit_context(), GFP_KERNEL, AUDIT_MAC_STATUS,
|
||||
"enforcing=%d old_enforcing=%d auid=%u ses=%u"
|
||||
" enabled=1 old-enabled=1 lsm=ipe res=1",
|
||||
new_enforce, old_enforce,
|
||||
from_kuid(&init_user_ns, audit_get_loginuid(current)),
|
||||
audit_get_sessionid(current));
|
||||
|
||||
audit_log_end(ab);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef _IPE_AUDIT_H
|
||||
#define _IPE_AUDIT_H
|
||||
|
||||
#include "policy.h"
|
||||
|
||||
void ipe_audit_match(const struct ipe_eval_ctx *const ctx,
|
||||
enum ipe_match match_type,
|
||||
enum ipe_action_type act, const struct ipe_rule *const r);
|
||||
void ipe_audit_policy_load(const struct ipe_policy *const p);
|
||||
void ipe_audit_policy_activation(const struct ipe_policy *const op,
|
||||
const struct ipe_policy *const np);
|
||||
void ipe_audit_enforce(bool new_enforce, bool old_enforce);
|
||||
|
||||
#endif /* _IPE_AUDIT_H */
|
||||
@@ -0,0 +1,118 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
|
||||
#include "digest.h"
|
||||
|
||||
/**
|
||||
* ipe_digest_parse() - parse a digest in IPE's policy.
|
||||
* @valstr: Supplies the string parsed from the policy.
|
||||
*
|
||||
* Digests in IPE are defined in a standard way:
|
||||
* <alg_name>:<hex>
|
||||
*
|
||||
* Use this function to create a property to parse the digest
|
||||
* consistently. The parsed digest will be saved in @value in IPE's
|
||||
* policy.
|
||||
*
|
||||
* Return: The parsed digest_info structure on success. If an error occurs,
|
||||
* the function will return the error value (via ERR_PTR).
|
||||
*/
|
||||
struct digest_info *ipe_digest_parse(const char *valstr)
|
||||
{
|
||||
struct digest_info *info = NULL;
|
||||
char *sep, *raw_digest;
|
||||
size_t raw_digest_len;
|
||||
u8 *digest = NULL;
|
||||
char *alg = NULL;
|
||||
int rc = 0;
|
||||
|
||||
info = kzalloc(sizeof(*info), GFP_KERNEL);
|
||||
if (!info)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
sep = strchr(valstr, ':');
|
||||
if (!sep) {
|
||||
rc = -EBADMSG;
|
||||
goto err;
|
||||
}
|
||||
|
||||
alg = kstrndup(valstr, sep - valstr, GFP_KERNEL);
|
||||
if (!alg) {
|
||||
rc = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
raw_digest = sep + 1;
|
||||
raw_digest_len = strlen(raw_digest);
|
||||
|
||||
info->digest_len = (raw_digest_len + 1) / 2;
|
||||
digest = kzalloc(info->digest_len, GFP_KERNEL);
|
||||
if (!digest) {
|
||||
rc = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
rc = hex2bin(digest, raw_digest, info->digest_len);
|
||||
if (rc < 0) {
|
||||
rc = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
info->alg = alg;
|
||||
info->digest = digest;
|
||||
return info;
|
||||
|
||||
err:
|
||||
kfree(alg);
|
||||
kfree(digest);
|
||||
kfree(info);
|
||||
return ERR_PTR(rc);
|
||||
}
|
||||
|
||||
/**
|
||||
* ipe_digest_eval() - evaluate an IPE digest against another digest.
|
||||
* @expected: Supplies the policy-provided digest value.
|
||||
* @digest: Supplies the digest to compare against the policy digest value.
|
||||
*
|
||||
* Return:
|
||||
* * %true - digests match
|
||||
* * %false - digests do not match
|
||||
*/
|
||||
bool ipe_digest_eval(const struct digest_info *expected,
|
||||
const struct digest_info *digest)
|
||||
{
|
||||
return (expected->digest_len == digest->digest_len) &&
|
||||
(!strcmp(expected->alg, digest->alg)) &&
|
||||
(!memcmp(expected->digest, digest->digest, expected->digest_len));
|
||||
}
|
||||
|
||||
/**
|
||||
* ipe_digest_free() - free an IPE digest.
|
||||
* @info: Supplies a pointer the policy-provided digest to free.
|
||||
*/
|
||||
void ipe_digest_free(struct digest_info *info)
|
||||
{
|
||||
if (IS_ERR_OR_NULL(info))
|
||||
return;
|
||||
|
||||
kfree(info->alg);
|
||||
kfree(info->digest);
|
||||
kfree(info);
|
||||
}
|
||||
|
||||
/**
|
||||
* ipe_digest_audit() - audit a digest that was sourced from IPE's policy.
|
||||
* @ab: Supplies the audit_buffer to append the formatted result.
|
||||
* @info: Supplies a pointer to source the audit record from.
|
||||
*
|
||||
* Digests in IPE are audited in this format:
|
||||
* <alg_name>:<hex>
|
||||
*/
|
||||
void ipe_digest_audit(struct audit_buffer *ab, const struct digest_info *info)
|
||||
{
|
||||
audit_log_untrustedstring(ab, info->alg);
|
||||
audit_log_format(ab, ":");
|
||||
audit_log_n_hex(ab, info->digest, info->digest_len);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef _IPE_DIGEST_H
|
||||
#define _IPE_DIGEST_H
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/audit.h>
|
||||
|
||||
#include "policy.h"
|
||||
|
||||
struct digest_info {
|
||||
const char *alg;
|
||||
const u8 *digest;
|
||||
size_t digest_len;
|
||||
};
|
||||
|
||||
struct digest_info *ipe_digest_parse(const char *valstr);
|
||||
void ipe_digest_free(struct digest_info *digest_info);
|
||||
void ipe_digest_audit(struct audit_buffer *ab, const struct digest_info *val);
|
||||
bool ipe_digest_eval(const struct digest_info *expected,
|
||||
const struct digest_info *digest);
|
||||
|
||||
#endif /* _IPE_DIGEST_H */
|
||||
@@ -0,0 +1,393 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/fs.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/rcupdate.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/fsverity.h>
|
||||
|
||||
#include "ipe.h"
|
||||
#include "eval.h"
|
||||
#include "policy.h"
|
||||
#include "audit.h"
|
||||
#include "digest.h"
|
||||
|
||||
struct ipe_policy __rcu *ipe_active_policy;
|
||||
bool success_audit;
|
||||
bool enforce = true;
|
||||
#define INO_BLOCK_DEV(ino) ((ino)->i_sb->s_bdev)
|
||||
|
||||
#define FILE_SUPERBLOCK(f) ((f)->f_path.mnt->mnt_sb)
|
||||
|
||||
/**
|
||||
* build_ipe_sb_ctx() - Build initramfs field of an ipe evaluation context.
|
||||
* @ctx: Supplies a pointer to the context to be populated.
|
||||
* @file: Supplies the file struct of the file triggered IPE event.
|
||||
*/
|
||||
static void build_ipe_sb_ctx(struct ipe_eval_ctx *ctx, const struct file *const file)
|
||||
{
|
||||
ctx->initramfs = ipe_sb(FILE_SUPERBLOCK(file))->initramfs;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_IPE_PROP_DM_VERITY
|
||||
/**
|
||||
* build_ipe_bdev_ctx() - Build ipe_bdev field of an evaluation context.
|
||||
* @ctx: Supplies a pointer to the context to be populated.
|
||||
* @ino: Supplies the inode struct of the file triggered IPE event.
|
||||
*/
|
||||
static void build_ipe_bdev_ctx(struct ipe_eval_ctx *ctx, const struct inode *const ino)
|
||||
{
|
||||
if (INO_BLOCK_DEV(ino))
|
||||
ctx->ipe_bdev = ipe_bdev(INO_BLOCK_DEV(ino));
|
||||
}
|
||||
#else
|
||||
static void build_ipe_bdev_ctx(struct ipe_eval_ctx *ctx, const struct inode *const ino)
|
||||
{
|
||||
}
|
||||
#endif /* CONFIG_IPE_PROP_DM_VERITY */
|
||||
|
||||
#ifdef CONFIG_IPE_PROP_FS_VERITY
|
||||
#ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG
|
||||
static void build_ipe_inode_blob_ctx(struct ipe_eval_ctx *ctx,
|
||||
const struct inode *const ino)
|
||||
{
|
||||
ctx->ipe_inode = ipe_inode(ctx->ino);
|
||||
}
|
||||
#else
|
||||
static inline void build_ipe_inode_blob_ctx(struct ipe_eval_ctx *ctx,
|
||||
const struct inode *const ino)
|
||||
{
|
||||
}
|
||||
#endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
|
||||
|
||||
/**
|
||||
* build_ipe_inode_ctx() - Build inode fields of an evaluation context.
|
||||
* @ctx: Supplies a pointer to the context to be populated.
|
||||
* @ino: Supplies the inode struct of the file triggered IPE event.
|
||||
*/
|
||||
static void build_ipe_inode_ctx(struct ipe_eval_ctx *ctx, const struct inode *const ino)
|
||||
{
|
||||
ctx->ino = ino;
|
||||
build_ipe_inode_blob_ctx(ctx, ino);
|
||||
}
|
||||
#else
|
||||
static void build_ipe_inode_ctx(struct ipe_eval_ctx *ctx, const struct inode *const ino)
|
||||
{
|
||||
}
|
||||
#endif /* CONFIG_IPE_PROP_FS_VERITY */
|
||||
|
||||
/**
|
||||
* ipe_build_eval_ctx() - Build an ipe evaluation context.
|
||||
* @ctx: Supplies a pointer to the context to be populated.
|
||||
* @file: Supplies a pointer to the file to associated with the evaluation.
|
||||
* @op: Supplies the IPE policy operation associated with the evaluation.
|
||||
* @hook: Supplies the LSM hook associated with the evaluation.
|
||||
*/
|
||||
void ipe_build_eval_ctx(struct ipe_eval_ctx *ctx,
|
||||
const struct file *file,
|
||||
enum ipe_op_type op,
|
||||
enum ipe_hook_type hook)
|
||||
{
|
||||
struct inode *ino;
|
||||
|
||||
ctx->file = file;
|
||||
ctx->op = op;
|
||||
ctx->hook = hook;
|
||||
|
||||
if (file) {
|
||||
build_ipe_sb_ctx(ctx, file);
|
||||
ino = d_real_inode(file->f_path.dentry);
|
||||
build_ipe_bdev_ctx(ctx, ino);
|
||||
build_ipe_inode_ctx(ctx, ino);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* evaluate_boot_verified() - Evaluate @ctx for the boot verified property.
|
||||
* @ctx: Supplies a pointer to the context being evaluated.
|
||||
*
|
||||
* Return:
|
||||
* * %true - The current @ctx match the @p
|
||||
* * %false - The current @ctx doesn't match the @p
|
||||
*/
|
||||
static bool evaluate_boot_verified(const struct ipe_eval_ctx *const ctx)
|
||||
{
|
||||
return ctx->initramfs;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_IPE_PROP_DM_VERITY
|
||||
/**
|
||||
* evaluate_dmv_roothash() - Evaluate @ctx against a dmv roothash property.
|
||||
* @ctx: Supplies a pointer to the context being evaluated.
|
||||
* @p: Supplies a pointer to the property being evaluated.
|
||||
*
|
||||
* Return:
|
||||
* * %true - The current @ctx match the @p
|
||||
* * %false - The current @ctx doesn't match the @p
|
||||
*/
|
||||
static bool evaluate_dmv_roothash(const struct ipe_eval_ctx *const ctx,
|
||||
struct ipe_prop *p)
|
||||
{
|
||||
return !!ctx->ipe_bdev &&
|
||||
!!ctx->ipe_bdev->root_hash &&
|
||||
ipe_digest_eval(p->value,
|
||||
ctx->ipe_bdev->root_hash);
|
||||
}
|
||||
#else
|
||||
static bool evaluate_dmv_roothash(const struct ipe_eval_ctx *const ctx,
|
||||
struct ipe_prop *p)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif /* CONFIG_IPE_PROP_DM_VERITY */
|
||||
|
||||
#ifdef CONFIG_IPE_PROP_DM_VERITY_SIGNATURE
|
||||
/**
|
||||
* evaluate_dmv_sig_false() - Evaluate @ctx against a dmv sig false property.
|
||||
* @ctx: Supplies a pointer to the context being evaluated.
|
||||
*
|
||||
* Return:
|
||||
* * %true - The current @ctx match the property
|
||||
* * %false - The current @ctx doesn't match the property
|
||||
*/
|
||||
static bool evaluate_dmv_sig_false(const struct ipe_eval_ctx *const ctx)
|
||||
{
|
||||
return !ctx->ipe_bdev || (!ctx->ipe_bdev->dm_verity_signed);
|
||||
}
|
||||
|
||||
/**
|
||||
* evaluate_dmv_sig_true() - Evaluate @ctx against a dmv sig true property.
|
||||
* @ctx: Supplies a pointer to the context being evaluated.
|
||||
*
|
||||
* Return:
|
||||
* * %true - The current @ctx match the property
|
||||
* * %false - The current @ctx doesn't match the property
|
||||
*/
|
||||
static bool evaluate_dmv_sig_true(const struct ipe_eval_ctx *const ctx)
|
||||
{
|
||||
return !evaluate_dmv_sig_false(ctx);
|
||||
}
|
||||
#else
|
||||
static bool evaluate_dmv_sig_false(const struct ipe_eval_ctx *const ctx)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool evaluate_dmv_sig_true(const struct ipe_eval_ctx *const ctx)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif /* CONFIG_IPE_PROP_DM_VERITY_SIGNATURE */
|
||||
|
||||
#ifdef CONFIG_IPE_PROP_FS_VERITY
|
||||
/**
|
||||
* evaluate_fsv_digest() - Evaluate @ctx against a fsv digest property.
|
||||
* @ctx: Supplies a pointer to the context being evaluated.
|
||||
* @p: Supplies a pointer to the property being evaluated.
|
||||
*
|
||||
* Return:
|
||||
* * %true - The current @ctx match the @p
|
||||
* * %false - The current @ctx doesn't match the @p
|
||||
*/
|
||||
static bool evaluate_fsv_digest(const struct ipe_eval_ctx *const ctx,
|
||||
struct ipe_prop *p)
|
||||
{
|
||||
enum hash_algo alg;
|
||||
u8 digest[FS_VERITY_MAX_DIGEST_SIZE];
|
||||
struct digest_info info;
|
||||
|
||||
if (!ctx->ino)
|
||||
return false;
|
||||
if (!fsverity_get_digest((struct inode *)ctx->ino,
|
||||
digest,
|
||||
NULL,
|
||||
&alg))
|
||||
return false;
|
||||
|
||||
info.alg = hash_algo_name[alg];
|
||||
info.digest = digest;
|
||||
info.digest_len = hash_digest_size[alg];
|
||||
|
||||
return ipe_digest_eval(p->value, &info);
|
||||
}
|
||||
#else
|
||||
static bool evaluate_fsv_digest(const struct ipe_eval_ctx *const ctx,
|
||||
struct ipe_prop *p)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif /* CONFIG_IPE_PROP_FS_VERITY */
|
||||
|
||||
#ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG
|
||||
/**
|
||||
* evaluate_fsv_sig_false() - Evaluate @ctx against a fsv sig false property.
|
||||
* @ctx: Supplies a pointer to the context being evaluated.
|
||||
*
|
||||
* Return:
|
||||
* * %true - The current @ctx match the property
|
||||
* * %false - The current @ctx doesn't match the property
|
||||
*/
|
||||
static bool evaluate_fsv_sig_false(const struct ipe_eval_ctx *const ctx)
|
||||
{
|
||||
return !ctx->ino ||
|
||||
!IS_VERITY(ctx->ino) ||
|
||||
!ctx->ipe_inode ||
|
||||
!ctx->ipe_inode->fs_verity_signed;
|
||||
}
|
||||
|
||||
/**
|
||||
* evaluate_fsv_sig_true() - Evaluate @ctx against a fsv sig true property.
|
||||
* @ctx: Supplies a pointer to the context being evaluated.
|
||||
*
|
||||
* Return:
|
||||
* * %true - The current @ctx match the property
|
||||
* * %false - The current @ctx doesn't match the property
|
||||
*/
|
||||
static bool evaluate_fsv_sig_true(const struct ipe_eval_ctx *const ctx)
|
||||
{
|
||||
return !evaluate_fsv_sig_false(ctx);
|
||||
}
|
||||
#else
|
||||
static bool evaluate_fsv_sig_false(const struct ipe_eval_ctx *const ctx)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool evaluate_fsv_sig_true(const struct ipe_eval_ctx *const ctx)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
|
||||
|
||||
/**
|
||||
* evaluate_property() - Analyze @ctx against a rule property.
|
||||
* @ctx: Supplies a pointer to the context to be evaluated.
|
||||
* @p: Supplies a pointer to the property to be evaluated.
|
||||
*
|
||||
* This function Determines whether the specified @ctx
|
||||
* matches the conditions defined by a rule property @p.
|
||||
*
|
||||
* Return:
|
||||
* * %true - The current @ctx match the @p
|
||||
* * %false - The current @ctx doesn't match the @p
|
||||
*/
|
||||
static bool evaluate_property(const struct ipe_eval_ctx *const ctx,
|
||||
struct ipe_prop *p)
|
||||
{
|
||||
switch (p->type) {
|
||||
case IPE_PROP_BOOT_VERIFIED_FALSE:
|
||||
return !evaluate_boot_verified(ctx);
|
||||
case IPE_PROP_BOOT_VERIFIED_TRUE:
|
||||
return evaluate_boot_verified(ctx);
|
||||
case IPE_PROP_DMV_ROOTHASH:
|
||||
return evaluate_dmv_roothash(ctx, p);
|
||||
case IPE_PROP_DMV_SIG_FALSE:
|
||||
return evaluate_dmv_sig_false(ctx);
|
||||
case IPE_PROP_DMV_SIG_TRUE:
|
||||
return evaluate_dmv_sig_true(ctx);
|
||||
case IPE_PROP_FSV_DIGEST:
|
||||
return evaluate_fsv_digest(ctx, p);
|
||||
case IPE_PROP_FSV_SIG_FALSE:
|
||||
return evaluate_fsv_sig_false(ctx);
|
||||
case IPE_PROP_FSV_SIG_TRUE:
|
||||
return evaluate_fsv_sig_true(ctx);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ipe_evaluate_event() - Analyze @ctx against the current active policy.
|
||||
* @ctx: Supplies a pointer to the context to be evaluated.
|
||||
*
|
||||
* This is the loop where all policy evaluations happen against the IPE policy.
|
||||
*
|
||||
* Return:
|
||||
* * %0 - Success
|
||||
* * %-EACCES - @ctx did not pass evaluation
|
||||
*/
|
||||
int ipe_evaluate_event(const struct ipe_eval_ctx *const ctx)
|
||||
{
|
||||
const struct ipe_op_table *rules = NULL;
|
||||
const struct ipe_rule *rule = NULL;
|
||||
struct ipe_policy *pol = NULL;
|
||||
struct ipe_prop *prop = NULL;
|
||||
enum ipe_action_type action;
|
||||
enum ipe_match match_type;
|
||||
bool match = false;
|
||||
int rc = 0;
|
||||
|
||||
rcu_read_lock();
|
||||
|
||||
pol = rcu_dereference(ipe_active_policy);
|
||||
if (!pol) {
|
||||
rcu_read_unlock();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ctx->op == IPE_OP_INVALID) {
|
||||
if (pol->parsed->global_default_action == IPE_ACTION_INVALID) {
|
||||
WARN(1, "no default rule set for unknown op, ALLOW it");
|
||||
action = IPE_ACTION_ALLOW;
|
||||
} else {
|
||||
action = pol->parsed->global_default_action;
|
||||
}
|
||||
match_type = IPE_MATCH_GLOBAL;
|
||||
goto eval;
|
||||
}
|
||||
|
||||
rules = &pol->parsed->rules[ctx->op];
|
||||
|
||||
list_for_each_entry(rule, &rules->rules, next) {
|
||||
match = true;
|
||||
|
||||
list_for_each_entry(prop, &rule->props, next) {
|
||||
match = evaluate_property(ctx, prop);
|
||||
if (!match)
|
||||
break;
|
||||
}
|
||||
|
||||
if (match)
|
||||
break;
|
||||
}
|
||||
|
||||
if (match) {
|
||||
action = rule->action;
|
||||
match_type = IPE_MATCH_RULE;
|
||||
} else if (rules->default_action != IPE_ACTION_INVALID) {
|
||||
action = rules->default_action;
|
||||
match_type = IPE_MATCH_TABLE;
|
||||
} else {
|
||||
action = pol->parsed->global_default_action;
|
||||
match_type = IPE_MATCH_GLOBAL;
|
||||
}
|
||||
|
||||
eval:
|
||||
ipe_audit_match(ctx, match_type, action, rule);
|
||||
rcu_read_unlock();
|
||||
|
||||
if (action == IPE_ACTION_DENY)
|
||||
rc = -EACCES;
|
||||
|
||||
if (!READ_ONCE(enforce))
|
||||
rc = 0;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Set the right module name */
|
||||
#ifdef KBUILD_MODNAME
|
||||
#undef KBUILD_MODNAME
|
||||
#define KBUILD_MODNAME "ipe"
|
||||
#endif
|
||||
|
||||
module_param(success_audit, bool, 0400);
|
||||
MODULE_PARM_DESC(success_audit, "Start IPE with success auditing enabled");
|
||||
module_param(enforce, bool, 0400);
|
||||
MODULE_PARM_DESC(enforce, "Start IPE in enforce or permissive mode");
|
||||
@@ -0,0 +1,70 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef _IPE_EVAL_H
|
||||
#define _IPE_EVAL_H
|
||||
|
||||
#include <linux/file.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "policy.h"
|
||||
#include "hooks.h"
|
||||
|
||||
#define IPE_EVAL_CTX_INIT ((struct ipe_eval_ctx){ 0 })
|
||||
|
||||
extern struct ipe_policy __rcu *ipe_active_policy;
|
||||
extern bool success_audit;
|
||||
extern bool enforce;
|
||||
|
||||
struct ipe_superblock {
|
||||
bool initramfs;
|
||||
};
|
||||
|
||||
#ifdef CONFIG_IPE_PROP_DM_VERITY
|
||||
struct ipe_bdev {
|
||||
#ifdef CONFIG_IPE_PROP_DM_VERITY_SIGNATURE
|
||||
bool dm_verity_signed;
|
||||
#endif /* CONFIG_IPE_PROP_DM_VERITY_SIGNATURE */
|
||||
struct digest_info *root_hash;
|
||||
};
|
||||
#endif /* CONFIG_IPE_PROP_DM_VERITY */
|
||||
|
||||
#ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG
|
||||
struct ipe_inode {
|
||||
bool fs_verity_signed;
|
||||
};
|
||||
#endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
|
||||
|
||||
struct ipe_eval_ctx {
|
||||
enum ipe_op_type op;
|
||||
enum ipe_hook_type hook;
|
||||
|
||||
const struct file *file;
|
||||
bool initramfs;
|
||||
#ifdef CONFIG_IPE_PROP_DM_VERITY
|
||||
const struct ipe_bdev *ipe_bdev;
|
||||
#endif /* CONFIG_IPE_PROP_DM_VERITY */
|
||||
#ifdef CONFIG_IPE_PROP_FS_VERITY
|
||||
const struct inode *ino;
|
||||
#endif /* CONFIG_IPE_PROP_FS_VERITY */
|
||||
#ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG
|
||||
const struct ipe_inode *ipe_inode;
|
||||
#endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
|
||||
};
|
||||
|
||||
enum ipe_match {
|
||||
IPE_MATCH_RULE = 0,
|
||||
IPE_MATCH_TABLE,
|
||||
IPE_MATCH_GLOBAL,
|
||||
__IPE_MATCH_MAX
|
||||
};
|
||||
|
||||
void ipe_build_eval_ctx(struct ipe_eval_ctx *ctx,
|
||||
const struct file *file,
|
||||
enum ipe_op_type op,
|
||||
enum ipe_hook_type hook);
|
||||
int ipe_evaluate_event(const struct ipe_eval_ctx *const ctx);
|
||||
|
||||
#endif /* _IPE_EVAL_H */
|
||||
@@ -0,0 +1,247 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/dcache.h>
|
||||
#include <linux/security.h>
|
||||
|
||||
#include "ipe.h"
|
||||
#include "fs.h"
|
||||
#include "eval.h"
|
||||
#include "policy.h"
|
||||
#include "audit.h"
|
||||
|
||||
static struct dentry *np __ro_after_init;
|
||||
static struct dentry *root __ro_after_init;
|
||||
struct dentry *policy_root __ro_after_init;
|
||||
static struct dentry *audit_node __ro_after_init;
|
||||
static struct dentry *enforce_node __ro_after_init;
|
||||
|
||||
/**
|
||||
* setaudit() - Write handler for the securityfs node, "ipe/success_audit"
|
||||
* @f: Supplies a file structure representing the securityfs node.
|
||||
* @data: Supplies a buffer passed to the write syscall.
|
||||
* @len: Supplies the length of @data.
|
||||
* @offset: unused.
|
||||
*
|
||||
* Return:
|
||||
* * Length of buffer written - Success
|
||||
* * %-EPERM - Insufficient permission
|
||||
*/
|
||||
static ssize_t setaudit(struct file *f, const char __user *data,
|
||||
size_t len, loff_t *offset)
|
||||
{
|
||||
int rc = 0;
|
||||
bool value;
|
||||
|
||||
if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
rc = kstrtobool_from_user(data, len, &value);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
WRITE_ONCE(success_audit, value);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* getaudit() - Read handler for the securityfs node, "ipe/success_audit"
|
||||
* @f: Supplies a file structure representing the securityfs node.
|
||||
* @data: Supplies a buffer passed to the read syscall.
|
||||
* @len: Supplies the length of @data.
|
||||
* @offset: unused.
|
||||
*
|
||||
* Return: Length of buffer written
|
||||
*/
|
||||
static ssize_t getaudit(struct file *f, char __user *data,
|
||||
size_t len, loff_t *offset)
|
||||
{
|
||||
const char *result;
|
||||
|
||||
result = ((READ_ONCE(success_audit)) ? "1" : "0");
|
||||
|
||||
return simple_read_from_buffer(data, len, offset, result, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* setenforce() - Write handler for the securityfs node, "ipe/enforce"
|
||||
* @f: Supplies a file structure representing the securityfs node.
|
||||
* @data: Supplies a buffer passed to the write syscall.
|
||||
* @len: Supplies the length of @data.
|
||||
* @offset: unused.
|
||||
*
|
||||
* Return:
|
||||
* * Length of buffer written - Success
|
||||
* * %-EPERM - Insufficient permission
|
||||
*/
|
||||
static ssize_t setenforce(struct file *f, const char __user *data,
|
||||
size_t len, loff_t *offset)
|
||||
{
|
||||
int rc = 0;
|
||||
bool new_value, old_value;
|
||||
|
||||
if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
old_value = READ_ONCE(enforce);
|
||||
rc = kstrtobool_from_user(data, len, &new_value);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
if (new_value != old_value) {
|
||||
ipe_audit_enforce(new_value, old_value);
|
||||
WRITE_ONCE(enforce, new_value);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* getenforce() - Read handler for the securityfs node, "ipe/enforce"
|
||||
* @f: Supplies a file structure representing the securityfs node.
|
||||
* @data: Supplies a buffer passed to the read syscall.
|
||||
* @len: Supplies the length of @data.
|
||||
* @offset: unused.
|
||||
*
|
||||
* Return: Length of buffer written
|
||||
*/
|
||||
static ssize_t getenforce(struct file *f, char __user *data,
|
||||
size_t len, loff_t *offset)
|
||||
{
|
||||
const char *result;
|
||||
|
||||
result = ((READ_ONCE(enforce)) ? "1" : "0");
|
||||
|
||||
return simple_read_from_buffer(data, len, offset, result, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* new_policy() - Write handler for the securityfs node, "ipe/new_policy".
|
||||
* @f: Supplies a file structure representing the securityfs node.
|
||||
* @data: Supplies a buffer passed to the write syscall.
|
||||
* @len: Supplies the length of @data.
|
||||
* @offset: unused.
|
||||
*
|
||||
* Return:
|
||||
* * Length of buffer written - Success
|
||||
* * %-EPERM - Insufficient permission
|
||||
* * %-ENOMEM - Out of memory (OOM)
|
||||
* * %-EBADMSG - Policy is invalid
|
||||
* * %-ERANGE - Policy version number overflow
|
||||
* * %-EINVAL - Policy version parsing error
|
||||
* * %-EEXIST - Same name policy already deployed
|
||||
*/
|
||||
static ssize_t new_policy(struct file *f, const char __user *data,
|
||||
size_t len, loff_t *offset)
|
||||
{
|
||||
struct ipe_policy *p = NULL;
|
||||
char *copy = NULL;
|
||||
int rc = 0;
|
||||
|
||||
if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
copy = memdup_user_nul(data, len);
|
||||
if (IS_ERR(copy))
|
||||
return PTR_ERR(copy);
|
||||
|
||||
p = ipe_new_policy(NULL, 0, copy, len);
|
||||
if (IS_ERR(p)) {
|
||||
rc = PTR_ERR(p);
|
||||
goto out;
|
||||
}
|
||||
|
||||
rc = ipe_new_policyfs_node(p);
|
||||
if (rc)
|
||||
goto out;
|
||||
|
||||
ipe_audit_policy_load(p);
|
||||
|
||||
out:
|
||||
if (rc < 0)
|
||||
ipe_free_policy(p);
|
||||
kfree(copy);
|
||||
return (rc < 0) ? rc : len;
|
||||
}
|
||||
|
||||
static const struct file_operations np_fops = {
|
||||
.write = new_policy,
|
||||
};
|
||||
|
||||
static const struct file_operations audit_fops = {
|
||||
.write = setaudit,
|
||||
.read = getaudit,
|
||||
};
|
||||
|
||||
static const struct file_operations enforce_fops = {
|
||||
.write = setenforce,
|
||||
.read = getenforce,
|
||||
};
|
||||
|
||||
/**
|
||||
* ipe_init_securityfs() - Initialize IPE's securityfs tree at fsinit.
|
||||
*
|
||||
* Return: %0 on success. If an error occurs, the function will return
|
||||
* the -errno.
|
||||
*/
|
||||
static int __init ipe_init_securityfs(void)
|
||||
{
|
||||
int rc = 0;
|
||||
struct ipe_policy *ap;
|
||||
|
||||
if (!ipe_enabled)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
root = securityfs_create_dir("ipe", NULL);
|
||||
if (IS_ERR(root)) {
|
||||
rc = PTR_ERR(root);
|
||||
goto err;
|
||||
}
|
||||
|
||||
audit_node = securityfs_create_file("success_audit", 0600, root,
|
||||
NULL, &audit_fops);
|
||||
if (IS_ERR(audit_node)) {
|
||||
rc = PTR_ERR(audit_node);
|
||||
goto err;
|
||||
}
|
||||
|
||||
enforce_node = securityfs_create_file("enforce", 0600, root, NULL,
|
||||
&enforce_fops);
|
||||
if (IS_ERR(enforce_node)) {
|
||||
rc = PTR_ERR(enforce_node);
|
||||
goto err;
|
||||
}
|
||||
|
||||
policy_root = securityfs_create_dir("policies", root);
|
||||
if (IS_ERR(policy_root)) {
|
||||
rc = PTR_ERR(policy_root);
|
||||
goto err;
|
||||
}
|
||||
|
||||
ap = rcu_access_pointer(ipe_active_policy);
|
||||
if (ap) {
|
||||
rc = ipe_new_policyfs_node(ap);
|
||||
if (rc)
|
||||
goto err;
|
||||
}
|
||||
|
||||
np = securityfs_create_file("new_policy", 0200, root, NULL, &np_fops);
|
||||
if (IS_ERR(np)) {
|
||||
rc = PTR_ERR(np);
|
||||
goto err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
err:
|
||||
securityfs_remove(np);
|
||||
securityfs_remove(policy_root);
|
||||
securityfs_remove(enforce_node);
|
||||
securityfs_remove(audit_node);
|
||||
securityfs_remove(root);
|
||||
return rc;
|
||||
}
|
||||
|
||||
fs_initcall(ipe_init_securityfs);
|
||||
@@ -0,0 +1,16 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef _IPE_FS_H
|
||||
#define _IPE_FS_H
|
||||
|
||||
#include "policy.h"
|
||||
|
||||
extern struct dentry *policy_root __ro_after_init;
|
||||
|
||||
int ipe_new_policyfs_node(struct ipe_policy *p);
|
||||
void ipe_del_policyfs_node(struct ipe_policy *p);
|
||||
|
||||
#endif /* _IPE_FS_H */
|
||||
@@ -0,0 +1,314 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/fs.h>
|
||||
#include <linux/fs_struct.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/binfmts.h>
|
||||
#include <linux/mman.h>
|
||||
#include <linux/blk_types.h>
|
||||
|
||||
#include "ipe.h"
|
||||
#include "hooks.h"
|
||||
#include "eval.h"
|
||||
#include "digest.h"
|
||||
|
||||
/**
|
||||
* ipe_bprm_check_security() - ipe security hook function for bprm check.
|
||||
* @bprm: Supplies a pointer to a linux_binprm structure to source the file
|
||||
* being evaluated.
|
||||
*
|
||||
* This LSM hook is called when a binary is loaded through the exec
|
||||
* family of system calls.
|
||||
*
|
||||
* Return:
|
||||
* * %0 - Success
|
||||
* * %-EACCES - Did not pass IPE policy
|
||||
*/
|
||||
int ipe_bprm_check_security(struct linux_binprm *bprm)
|
||||
{
|
||||
struct ipe_eval_ctx ctx = IPE_EVAL_CTX_INIT;
|
||||
|
||||
ipe_build_eval_ctx(&ctx, bprm->file, IPE_OP_EXEC, IPE_HOOK_BPRM_CHECK);
|
||||
return ipe_evaluate_event(&ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* ipe_mmap_file() - ipe security hook function for mmap check.
|
||||
* @f: File being mmap'd. Can be NULL in the case of anonymous memory.
|
||||
* @reqprot: The requested protection on the mmap, passed from usermode.
|
||||
* @prot: The effective protection on the mmap, resolved from reqprot and
|
||||
* system configuration.
|
||||
* @flags: Unused.
|
||||
*
|
||||
* This hook is called when a file is loaded through the mmap
|
||||
* family of system calls.
|
||||
*
|
||||
* Return:
|
||||
* * %0 - Success
|
||||
* * %-EACCES - Did not pass IPE policy
|
||||
*/
|
||||
int ipe_mmap_file(struct file *f, unsigned long reqprot __always_unused,
|
||||
unsigned long prot, unsigned long flags)
|
||||
{
|
||||
struct ipe_eval_ctx ctx = IPE_EVAL_CTX_INIT;
|
||||
|
||||
if (prot & PROT_EXEC) {
|
||||
ipe_build_eval_ctx(&ctx, f, IPE_OP_EXEC, IPE_HOOK_MMAP);
|
||||
return ipe_evaluate_event(&ctx);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* ipe_file_mprotect() - ipe security hook function for mprotect check.
|
||||
* @vma: Existing virtual memory area created by mmap or similar.
|
||||
* @reqprot: The requested protection on the mmap, passed from usermode.
|
||||
* @prot: The effective protection on the mmap, resolved from reqprot and
|
||||
* system configuration.
|
||||
*
|
||||
* This LSM hook is called when a mmap'd region of memory is changing
|
||||
* its protections via mprotect.
|
||||
*
|
||||
* Return:
|
||||
* * %0 - Success
|
||||
* * %-EACCES - Did not pass IPE policy
|
||||
*/
|
||||
int ipe_file_mprotect(struct vm_area_struct *vma,
|
||||
unsigned long reqprot __always_unused,
|
||||
unsigned long prot)
|
||||
{
|
||||
struct ipe_eval_ctx ctx = IPE_EVAL_CTX_INIT;
|
||||
|
||||
/* Already Executable */
|
||||
if (vma->vm_flags & VM_EXEC)
|
||||
return 0;
|
||||
|
||||
if (prot & PROT_EXEC) {
|
||||
ipe_build_eval_ctx(&ctx, vma->vm_file, IPE_OP_EXEC, IPE_HOOK_MPROTECT);
|
||||
return ipe_evaluate_event(&ctx);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* ipe_kernel_read_file() - ipe security hook function for kernel read.
|
||||
* @file: Supplies a pointer to the file structure being read in from disk.
|
||||
* @id: Supplies the enumeration identifying the purpose of the read.
|
||||
* @contents: Unused.
|
||||
*
|
||||
* This LSM hook is called when a file is read from disk in the kernel.
|
||||
*
|
||||
* Return:
|
||||
* * %0 - Success
|
||||
* * %-EACCES - Did not pass IPE policy
|
||||
*/
|
||||
int ipe_kernel_read_file(struct file *file, enum kernel_read_file_id id,
|
||||
bool contents)
|
||||
{
|
||||
struct ipe_eval_ctx ctx = IPE_EVAL_CTX_INIT;
|
||||
enum ipe_op_type op;
|
||||
|
||||
switch (id) {
|
||||
case READING_FIRMWARE:
|
||||
op = IPE_OP_FIRMWARE;
|
||||
break;
|
||||
case READING_MODULE:
|
||||
op = IPE_OP_KERNEL_MODULE;
|
||||
break;
|
||||
case READING_KEXEC_INITRAMFS:
|
||||
op = IPE_OP_KEXEC_INITRAMFS;
|
||||
break;
|
||||
case READING_KEXEC_IMAGE:
|
||||
op = IPE_OP_KEXEC_IMAGE;
|
||||
break;
|
||||
case READING_POLICY:
|
||||
op = IPE_OP_POLICY;
|
||||
break;
|
||||
case READING_X509_CERTIFICATE:
|
||||
op = IPE_OP_X509;
|
||||
break;
|
||||
default:
|
||||
op = IPE_OP_INVALID;
|
||||
WARN(1, "no rule setup for kernel_read_file enum %d", id);
|
||||
}
|
||||
|
||||
ipe_build_eval_ctx(&ctx, file, op, IPE_HOOK_KERNEL_READ);
|
||||
return ipe_evaluate_event(&ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* ipe_kernel_load_data() - ipe security hook function for kernel load data.
|
||||
* @id: Supplies the enumeration identifying the purpose of the load.
|
||||
* @contents: Unused.
|
||||
*
|
||||
* This LSM hook is called when a data buffer provided by userspace is loading
|
||||
* into the kernel.
|
||||
*
|
||||
* Return:
|
||||
* * %0 - Success
|
||||
* * %-EACCES - Did not pass IPE policy
|
||||
*/
|
||||
int ipe_kernel_load_data(enum kernel_load_data_id id, bool contents)
|
||||
{
|
||||
struct ipe_eval_ctx ctx = IPE_EVAL_CTX_INIT;
|
||||
enum ipe_op_type op;
|
||||
|
||||
switch (id) {
|
||||
case LOADING_FIRMWARE:
|
||||
op = IPE_OP_FIRMWARE;
|
||||
break;
|
||||
case LOADING_MODULE:
|
||||
op = IPE_OP_KERNEL_MODULE;
|
||||
break;
|
||||
case LOADING_KEXEC_INITRAMFS:
|
||||
op = IPE_OP_KEXEC_INITRAMFS;
|
||||
break;
|
||||
case LOADING_KEXEC_IMAGE:
|
||||
op = IPE_OP_KEXEC_IMAGE;
|
||||
break;
|
||||
case LOADING_POLICY:
|
||||
op = IPE_OP_POLICY;
|
||||
break;
|
||||
case LOADING_X509_CERTIFICATE:
|
||||
op = IPE_OP_X509;
|
||||
break;
|
||||
default:
|
||||
op = IPE_OP_INVALID;
|
||||
WARN(1, "no rule setup for kernel_load_data enum %d", id);
|
||||
}
|
||||
|
||||
ipe_build_eval_ctx(&ctx, NULL, op, IPE_HOOK_KERNEL_LOAD);
|
||||
return ipe_evaluate_event(&ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* ipe_unpack_initramfs() - Mark the current rootfs as initramfs.
|
||||
*/
|
||||
void ipe_unpack_initramfs(void)
|
||||
{
|
||||
ipe_sb(current->fs->root.mnt->mnt_sb)->initramfs = true;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_IPE_PROP_DM_VERITY
|
||||
/**
|
||||
* ipe_bdev_free_security() - Free IPE's LSM blob of block_devices.
|
||||
* @bdev: Supplies a pointer to a block_device that contains the structure
|
||||
* to free.
|
||||
*/
|
||||
void ipe_bdev_free_security(struct block_device *bdev)
|
||||
{
|
||||
struct ipe_bdev *blob = ipe_bdev(bdev);
|
||||
|
||||
ipe_digest_free(blob->root_hash);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_IPE_PROP_DM_VERITY_SIGNATURE
|
||||
static void ipe_set_dmverity_signature(struct ipe_bdev *blob,
|
||||
const void *value,
|
||||
size_t size)
|
||||
{
|
||||
blob->dm_verity_signed = size > 0 && value;
|
||||
}
|
||||
#else
|
||||
static inline void ipe_set_dmverity_signature(struct ipe_bdev *blob,
|
||||
const void *value,
|
||||
size_t size)
|
||||
{
|
||||
}
|
||||
#endif /* CONFIG_IPE_PROP_DM_VERITY_SIGNATURE */
|
||||
|
||||
/**
|
||||
* ipe_bdev_setintegrity() - Save integrity data from a bdev to IPE's LSM blob.
|
||||
* @bdev: Supplies a pointer to a block_device that contains the LSM blob.
|
||||
* @type: Supplies the integrity type.
|
||||
* @value: Supplies the value to store.
|
||||
* @size: The size of @value.
|
||||
*
|
||||
* This hook is currently used to save dm-verity's root hash or the existence
|
||||
* of a validated signed dm-verity root hash into LSM blob.
|
||||
*
|
||||
* Return: %0 on success. If an error occurs, the function will return the
|
||||
* -errno.
|
||||
*/
|
||||
int ipe_bdev_setintegrity(struct block_device *bdev, enum lsm_integrity_type type,
|
||||
const void *value, size_t size)
|
||||
{
|
||||
const struct dm_verity_digest *digest = NULL;
|
||||
struct ipe_bdev *blob = ipe_bdev(bdev);
|
||||
struct digest_info *info = NULL;
|
||||
|
||||
if (type == LSM_INT_DMVERITY_SIG_VALID) {
|
||||
ipe_set_dmverity_signature(blob, value, size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (type != LSM_INT_DMVERITY_ROOTHASH)
|
||||
return -EINVAL;
|
||||
|
||||
if (!value) {
|
||||
ipe_digest_free(blob->root_hash);
|
||||
blob->root_hash = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
digest = value;
|
||||
|
||||
info = kzalloc(sizeof(*info), GFP_KERNEL);
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
|
||||
info->digest = kmemdup(digest->digest, digest->digest_len, GFP_KERNEL);
|
||||
if (!info->digest)
|
||||
goto err;
|
||||
|
||||
info->alg = kstrdup(digest->alg, GFP_KERNEL);
|
||||
if (!info->alg)
|
||||
goto err;
|
||||
|
||||
info->digest_len = digest->digest_len;
|
||||
|
||||
ipe_digest_free(blob->root_hash);
|
||||
blob->root_hash = info;
|
||||
|
||||
return 0;
|
||||
err:
|
||||
ipe_digest_free(info);
|
||||
|
||||
return -ENOMEM;
|
||||
}
|
||||
#endif /* CONFIG_IPE_PROP_DM_VERITY */
|
||||
|
||||
#ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG
|
||||
/**
|
||||
* ipe_inode_setintegrity() - save integrity data from a inode to IPE's LSM blob.
|
||||
* @inode: The inode to source the security blob from.
|
||||
* @type: Supplies the integrity type.
|
||||
* @value: The value to be stored.
|
||||
* @size: The size of @value.
|
||||
*
|
||||
* This hook is currently used to save the existence of a validated fs-verity
|
||||
* builtin signature into LSM blob.
|
||||
*
|
||||
* Return: %0 on success. If an error occurs, the function will return the
|
||||
* -errno.
|
||||
*/
|
||||
int ipe_inode_setintegrity(const struct inode *inode,
|
||||
enum lsm_integrity_type type,
|
||||
const void *value, size_t size)
|
||||
{
|
||||
struct ipe_inode *inode_sec = ipe_inode(inode);
|
||||
|
||||
if (type == LSM_INT_FSVERITY_BUILTINSIG_VALID) {
|
||||
inode_sec->fs_verity_signed = size > 0 && value;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
#endif /* CONFIG_CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
|
||||
@@ -0,0 +1,52 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
#ifndef _IPE_HOOKS_H
|
||||
#define _IPE_HOOKS_H
|
||||
|
||||
#include <linux/fs.h>
|
||||
#include <linux/binfmts.h>
|
||||
#include <linux/security.h>
|
||||
#include <linux/blk_types.h>
|
||||
#include <linux/fsverity.h>
|
||||
|
||||
enum ipe_hook_type {
|
||||
IPE_HOOK_BPRM_CHECK = 0,
|
||||
IPE_HOOK_MMAP,
|
||||
IPE_HOOK_MPROTECT,
|
||||
IPE_HOOK_KERNEL_READ,
|
||||
IPE_HOOK_KERNEL_LOAD,
|
||||
__IPE_HOOK_MAX
|
||||
};
|
||||
|
||||
#define IPE_HOOK_INVALID __IPE_HOOK_MAX
|
||||
|
||||
int ipe_bprm_check_security(struct linux_binprm *bprm);
|
||||
|
||||
int ipe_mmap_file(struct file *f, unsigned long reqprot, unsigned long prot,
|
||||
unsigned long flags);
|
||||
|
||||
int ipe_file_mprotect(struct vm_area_struct *vma, unsigned long reqprot,
|
||||
unsigned long prot);
|
||||
|
||||
int ipe_kernel_read_file(struct file *file, enum kernel_read_file_id id,
|
||||
bool contents);
|
||||
|
||||
int ipe_kernel_load_data(enum kernel_load_data_id id, bool contents);
|
||||
|
||||
void ipe_unpack_initramfs(void);
|
||||
|
||||
#ifdef CONFIG_IPE_PROP_DM_VERITY
|
||||
void ipe_bdev_free_security(struct block_device *bdev);
|
||||
|
||||
int ipe_bdev_setintegrity(struct block_device *bdev, enum lsm_integrity_type type,
|
||||
const void *value, size_t len);
|
||||
#endif /* CONFIG_IPE_PROP_DM_VERITY */
|
||||
|
||||
#ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG
|
||||
int ipe_inode_setintegrity(const struct inode *inode, enum lsm_integrity_type type,
|
||||
const void *value, size_t size);
|
||||
#endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
|
||||
|
||||
#endif /* _IPE_HOOKS_H */
|
||||
@@ -0,0 +1,98 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
#include <uapi/linux/lsm.h>
|
||||
|
||||
#include "ipe.h"
|
||||
#include "eval.h"
|
||||
#include "hooks.h"
|
||||
|
||||
extern const char *const ipe_boot_policy;
|
||||
bool ipe_enabled;
|
||||
|
||||
static struct lsm_blob_sizes ipe_blobs __ro_after_init = {
|
||||
.lbs_superblock = sizeof(struct ipe_superblock),
|
||||
#ifdef CONFIG_IPE_PROP_DM_VERITY
|
||||
.lbs_bdev = sizeof(struct ipe_bdev),
|
||||
#endif /* CONFIG_IPE_PROP_DM_VERITY */
|
||||
#ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG
|
||||
.lbs_inode = sizeof(struct ipe_inode),
|
||||
#endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
|
||||
};
|
||||
|
||||
static const struct lsm_id ipe_lsmid = {
|
||||
.name = "ipe",
|
||||
.id = LSM_ID_IPE,
|
||||
};
|
||||
|
||||
struct ipe_superblock *ipe_sb(const struct super_block *sb)
|
||||
{
|
||||
return sb->s_security + ipe_blobs.lbs_superblock;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_IPE_PROP_DM_VERITY
|
||||
struct ipe_bdev *ipe_bdev(struct block_device *b)
|
||||
{
|
||||
return b->bd_security + ipe_blobs.lbs_bdev;
|
||||
}
|
||||
#endif /* CONFIG_IPE_PROP_DM_VERITY */
|
||||
|
||||
#ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG
|
||||
struct ipe_inode *ipe_inode(const struct inode *inode)
|
||||
{
|
||||
return inode->i_security + ipe_blobs.lbs_inode;
|
||||
}
|
||||
#endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
|
||||
|
||||
static struct security_hook_list ipe_hooks[] __ro_after_init = {
|
||||
LSM_HOOK_INIT(bprm_check_security, ipe_bprm_check_security),
|
||||
LSM_HOOK_INIT(mmap_file, ipe_mmap_file),
|
||||
LSM_HOOK_INIT(file_mprotect, ipe_file_mprotect),
|
||||
LSM_HOOK_INIT(kernel_read_file, ipe_kernel_read_file),
|
||||
LSM_HOOK_INIT(kernel_load_data, ipe_kernel_load_data),
|
||||
LSM_HOOK_INIT(initramfs_populated, ipe_unpack_initramfs),
|
||||
#ifdef CONFIG_IPE_PROP_DM_VERITY
|
||||
LSM_HOOK_INIT(bdev_free_security, ipe_bdev_free_security),
|
||||
LSM_HOOK_INIT(bdev_setintegrity, ipe_bdev_setintegrity),
|
||||
#endif /* CONFIG_IPE_PROP_DM_VERITY */
|
||||
#ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG
|
||||
LSM_HOOK_INIT(inode_setintegrity, ipe_inode_setintegrity),
|
||||
#endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
|
||||
};
|
||||
|
||||
/**
|
||||
* ipe_init() - Entry point of IPE.
|
||||
*
|
||||
* This is called at LSM init, which happens occurs early during kernel
|
||||
* start up. During this phase, IPE registers its hooks and loads the
|
||||
* builtin boot policy.
|
||||
*
|
||||
* Return:
|
||||
* * %0 - OK
|
||||
* * %-ENOMEM - Out of memory (OOM)
|
||||
*/
|
||||
static int __init ipe_init(void)
|
||||
{
|
||||
struct ipe_policy *p = NULL;
|
||||
|
||||
security_add_hooks(ipe_hooks, ARRAY_SIZE(ipe_hooks), &ipe_lsmid);
|
||||
ipe_enabled = true;
|
||||
|
||||
if (ipe_boot_policy) {
|
||||
p = ipe_new_policy(ipe_boot_policy, strlen(ipe_boot_policy),
|
||||
NULL, 0);
|
||||
if (IS_ERR(p))
|
||||
return PTR_ERR(p);
|
||||
|
||||
rcu_assign_pointer(ipe_active_policy, p);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_LSM(ipe) = {
|
||||
.name = "ipe",
|
||||
.init = ipe_init,
|
||||
.blobs = &ipe_blobs,
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef _IPE_H
|
||||
#define _IPE_H
|
||||
|
||||
#ifdef pr_fmt
|
||||
#undef pr_fmt
|
||||
#endif
|
||||
#define pr_fmt(fmt) "ipe: " fmt
|
||||
|
||||
#include <linux/lsm_hooks.h>
|
||||
struct ipe_superblock *ipe_sb(const struct super_block *sb);
|
||||
|
||||
extern bool ipe_enabled;
|
||||
|
||||
#ifdef CONFIG_IPE_PROP_DM_VERITY
|
||||
struct ipe_bdev *ipe_bdev(struct block_device *b);
|
||||
#endif /* CONFIG_IPE_PROP_DM_VERITY */
|
||||
#ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG
|
||||
struct ipe_inode *ipe_inode(const struct inode *inode);
|
||||
#endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */
|
||||
|
||||
#endif /* _IPE_H */
|
||||
@@ -0,0 +1,227 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/errno.h>
|
||||
#include <linux/verification.h>
|
||||
|
||||
#include "ipe.h"
|
||||
#include "eval.h"
|
||||
#include "fs.h"
|
||||
#include "policy.h"
|
||||
#include "policy_parser.h"
|
||||
#include "audit.h"
|
||||
|
||||
/* lock for synchronizing writers across ipe policy */
|
||||
DEFINE_MUTEX(ipe_policy_lock);
|
||||
|
||||
/**
|
||||
* ver_to_u64() - Convert an internal ipe_policy_version to a u64.
|
||||
* @p: Policy to extract the version from.
|
||||
*
|
||||
* Bits (LSB is index 0):
|
||||
* [48,32] -> Major
|
||||
* [32,16] -> Minor
|
||||
* [16, 0] -> Revision
|
||||
*
|
||||
* Return: u64 version of the embedded version structure.
|
||||
*/
|
||||
static inline u64 ver_to_u64(const struct ipe_policy *const p)
|
||||
{
|
||||
u64 r;
|
||||
|
||||
r = (((u64)p->parsed->version.major) << 32)
|
||||
| (((u64)p->parsed->version.minor) << 16)
|
||||
| ((u64)(p->parsed->version.rev));
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* ipe_free_policy() - Deallocate a given IPE policy.
|
||||
* @p: Supplies the policy to free.
|
||||
*
|
||||
* Safe to call on IS_ERR/NULL.
|
||||
*/
|
||||
void ipe_free_policy(struct ipe_policy *p)
|
||||
{
|
||||
if (IS_ERR_OR_NULL(p))
|
||||
return;
|
||||
|
||||
ipe_del_policyfs_node(p);
|
||||
ipe_free_parsed_policy(p->parsed);
|
||||
/*
|
||||
* p->text is allocated only when p->pkcs7 is not NULL
|
||||
* otherwise it points to the plaintext data inside the pkcs7
|
||||
*/
|
||||
if (!p->pkcs7)
|
||||
kfree(p->text);
|
||||
kfree(p->pkcs7);
|
||||
kfree(p);
|
||||
}
|
||||
|
||||
static int set_pkcs7_data(void *ctx, const void *data, size_t len,
|
||||
size_t asn1hdrlen __always_unused)
|
||||
{
|
||||
struct ipe_policy *p = ctx;
|
||||
|
||||
p->text = (const char *)data;
|
||||
p->textlen = len;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* ipe_update_policy() - parse a new policy and replace old with it.
|
||||
* @root: Supplies a pointer to the securityfs inode saved the policy.
|
||||
* @text: Supplies a pointer to the plain text policy.
|
||||
* @textlen: Supplies the length of @text.
|
||||
* @pkcs7: Supplies a pointer to a buffer containing a pkcs7 message.
|
||||
* @pkcs7len: Supplies the length of @pkcs7len.
|
||||
*
|
||||
* @text/@textlen is mutually exclusive with @pkcs7/@pkcs7len - see
|
||||
* ipe_new_policy.
|
||||
*
|
||||
* Context: Requires root->i_rwsem to be held.
|
||||
* Return: %0 on success. If an error occurs, the function will return
|
||||
* the -errno.
|
||||
*/
|
||||
int ipe_update_policy(struct inode *root, const char *text, size_t textlen,
|
||||
const char *pkcs7, size_t pkcs7len)
|
||||
{
|
||||
struct ipe_policy *old, *ap, *new = NULL;
|
||||
int rc = 0;
|
||||
|
||||
old = (struct ipe_policy *)root->i_private;
|
||||
if (!old)
|
||||
return -ENOENT;
|
||||
|
||||
new = ipe_new_policy(text, textlen, pkcs7, pkcs7len);
|
||||
if (IS_ERR(new))
|
||||
return PTR_ERR(new);
|
||||
|
||||
if (strcmp(new->parsed->name, old->parsed->name)) {
|
||||
rc = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (ver_to_u64(old) > ver_to_u64(new)) {
|
||||
rc = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
root->i_private = new;
|
||||
swap(new->policyfs, old->policyfs);
|
||||
ipe_audit_policy_load(new);
|
||||
|
||||
mutex_lock(&ipe_policy_lock);
|
||||
ap = rcu_dereference_protected(ipe_active_policy,
|
||||
lockdep_is_held(&ipe_policy_lock));
|
||||
if (old == ap) {
|
||||
rcu_assign_pointer(ipe_active_policy, new);
|
||||
mutex_unlock(&ipe_policy_lock);
|
||||
ipe_audit_policy_activation(old, new);
|
||||
} else {
|
||||
mutex_unlock(&ipe_policy_lock);
|
||||
}
|
||||
synchronize_rcu();
|
||||
ipe_free_policy(old);
|
||||
|
||||
return 0;
|
||||
err:
|
||||
ipe_free_policy(new);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* ipe_new_policy() - Allocate and parse an ipe_policy structure.
|
||||
*
|
||||
* @text: Supplies a pointer to the plain-text policy to parse.
|
||||
* @textlen: Supplies the length of @text.
|
||||
* @pkcs7: Supplies a pointer to a pkcs7-signed IPE policy.
|
||||
* @pkcs7len: Supplies the length of @pkcs7.
|
||||
*
|
||||
* @text/@textlen Should be NULL/0 if @pkcs7/@pkcs7len is set.
|
||||
*
|
||||
* Return:
|
||||
* * a pointer to the ipe_policy structure - Success
|
||||
* * %-EBADMSG - Policy is invalid
|
||||
* * %-ENOMEM - Out of memory (OOM)
|
||||
* * %-ERANGE - Policy version number overflow
|
||||
* * %-EINVAL - Policy version parsing error
|
||||
*/
|
||||
struct ipe_policy *ipe_new_policy(const char *text, size_t textlen,
|
||||
const char *pkcs7, size_t pkcs7len)
|
||||
{
|
||||
struct ipe_policy *new = NULL;
|
||||
int rc = 0;
|
||||
|
||||
new = kzalloc(sizeof(*new), GFP_KERNEL);
|
||||
if (!new)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
if (!text) {
|
||||
new->pkcs7len = pkcs7len;
|
||||
new->pkcs7 = kmemdup(pkcs7, pkcs7len, GFP_KERNEL);
|
||||
if (!new->pkcs7) {
|
||||
rc = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
rc = verify_pkcs7_signature(NULL, 0, new->pkcs7, pkcs7len, NULL,
|
||||
VERIFYING_UNSPECIFIED_SIGNATURE,
|
||||
set_pkcs7_data, new);
|
||||
if (rc)
|
||||
goto err;
|
||||
} else {
|
||||
new->textlen = textlen;
|
||||
new->text = kstrdup(text, GFP_KERNEL);
|
||||
if (!new->text) {
|
||||
rc = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
rc = ipe_parse_policy(new);
|
||||
if (rc)
|
||||
goto err;
|
||||
|
||||
return new;
|
||||
err:
|
||||
ipe_free_policy(new);
|
||||
return ERR_PTR(rc);
|
||||
}
|
||||
|
||||
/**
|
||||
* ipe_set_active_pol() - Make @p the active policy.
|
||||
* @p: Supplies a pointer to the policy to make active.
|
||||
*
|
||||
* Context: Requires root->i_rwsem, which i_private has the policy, to be held.
|
||||
* Return:
|
||||
* * %0 - Success
|
||||
* * %-EINVAL - New active policy version is invalid
|
||||
*/
|
||||
int ipe_set_active_pol(const struct ipe_policy *p)
|
||||
{
|
||||
struct ipe_policy *ap = NULL;
|
||||
|
||||
mutex_lock(&ipe_policy_lock);
|
||||
|
||||
ap = rcu_dereference_protected(ipe_active_policy,
|
||||
lockdep_is_held(&ipe_policy_lock));
|
||||
if (ap == p) {
|
||||
mutex_unlock(&ipe_policy_lock);
|
||||
return 0;
|
||||
}
|
||||
if (ap && ver_to_u64(ap) > ver_to_u64(p)) {
|
||||
mutex_unlock(&ipe_policy_lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rcu_assign_pointer(ipe_active_policy, p);
|
||||
ipe_audit_policy_activation(ap, p);
|
||||
mutex_unlock(&ipe_policy_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
#ifndef _IPE_POLICY_H
|
||||
#define _IPE_POLICY_H
|
||||
|
||||
#include <linux/list.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/fs.h>
|
||||
|
||||
enum ipe_op_type {
|
||||
IPE_OP_EXEC = 0,
|
||||
IPE_OP_FIRMWARE,
|
||||
IPE_OP_KERNEL_MODULE,
|
||||
IPE_OP_KEXEC_IMAGE,
|
||||
IPE_OP_KEXEC_INITRAMFS,
|
||||
IPE_OP_POLICY,
|
||||
IPE_OP_X509,
|
||||
__IPE_OP_MAX,
|
||||
};
|
||||
|
||||
#define IPE_OP_INVALID __IPE_OP_MAX
|
||||
|
||||
enum ipe_action_type {
|
||||
IPE_ACTION_ALLOW = 0,
|
||||
IPE_ACTION_DENY,
|
||||
__IPE_ACTION_MAX
|
||||
};
|
||||
|
||||
#define IPE_ACTION_INVALID __IPE_ACTION_MAX
|
||||
|
||||
enum ipe_prop_type {
|
||||
IPE_PROP_BOOT_VERIFIED_FALSE,
|
||||
IPE_PROP_BOOT_VERIFIED_TRUE,
|
||||
IPE_PROP_DMV_ROOTHASH,
|
||||
IPE_PROP_DMV_SIG_FALSE,
|
||||
IPE_PROP_DMV_SIG_TRUE,
|
||||
IPE_PROP_FSV_DIGEST,
|
||||
IPE_PROP_FSV_SIG_FALSE,
|
||||
IPE_PROP_FSV_SIG_TRUE,
|
||||
__IPE_PROP_MAX
|
||||
};
|
||||
|
||||
#define IPE_PROP_INVALID __IPE_PROP_MAX
|
||||
|
||||
struct ipe_prop {
|
||||
struct list_head next;
|
||||
enum ipe_prop_type type;
|
||||
void *value;
|
||||
};
|
||||
|
||||
struct ipe_rule {
|
||||
enum ipe_op_type op;
|
||||
enum ipe_action_type action;
|
||||
struct list_head props;
|
||||
struct list_head next;
|
||||
};
|
||||
|
||||
struct ipe_op_table {
|
||||
struct list_head rules;
|
||||
enum ipe_action_type default_action;
|
||||
};
|
||||
|
||||
struct ipe_parsed_policy {
|
||||
const char *name;
|
||||
struct {
|
||||
u16 major;
|
||||
u16 minor;
|
||||
u16 rev;
|
||||
} version;
|
||||
|
||||
enum ipe_action_type global_default_action;
|
||||
|
||||
struct ipe_op_table rules[__IPE_OP_MAX];
|
||||
};
|
||||
|
||||
struct ipe_policy {
|
||||
const char *pkcs7;
|
||||
size_t pkcs7len;
|
||||
|
||||
const char *text;
|
||||
size_t textlen;
|
||||
|
||||
struct ipe_parsed_policy *parsed;
|
||||
|
||||
struct dentry *policyfs;
|
||||
};
|
||||
|
||||
struct ipe_policy *ipe_new_policy(const char *text, size_t textlen,
|
||||
const char *pkcs7, size_t pkcs7len);
|
||||
void ipe_free_policy(struct ipe_policy *pol);
|
||||
int ipe_update_policy(struct inode *root, const char *text, size_t textlen,
|
||||
const char *pkcs7, size_t pkcs7len);
|
||||
int ipe_set_active_pol(const struct ipe_policy *p);
|
||||
extern struct mutex ipe_policy_lock;
|
||||
|
||||
#endif /* _IPE_POLICY_H */
|
||||
@@ -0,0 +1,472 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
#include <linux/fs.h>
|
||||
#include <linux/namei.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/dcache.h>
|
||||
#include <linux/security.h>
|
||||
|
||||
#include "ipe.h"
|
||||
#include "policy.h"
|
||||
#include "eval.h"
|
||||
#include "fs.h"
|
||||
|
||||
#define MAX_VERSION_SIZE ARRAY_SIZE("65535.65535.65535")
|
||||
|
||||
/**
|
||||
* ipefs_file - defines a file in securityfs.
|
||||
*/
|
||||
struct ipefs_file {
|
||||
const char *name;
|
||||
umode_t access;
|
||||
const struct file_operations *fops;
|
||||
};
|
||||
|
||||
/**
|
||||
* read_pkcs7() - Read handler for "ipe/policies/$name/pkcs7".
|
||||
* @f: Supplies a file structure representing the securityfs node.
|
||||
* @data: Supplies a buffer passed to the write syscall.
|
||||
* @len: Supplies the length of @data.
|
||||
* @offset: unused.
|
||||
*
|
||||
* @data will be populated with the pkcs7 blob representing the policy
|
||||
* on success. If the policy is unsigned (like the boot policy), this
|
||||
* will return -ENOENT.
|
||||
*
|
||||
* Return:
|
||||
* * Length of buffer written - Success
|
||||
* * %-ENOENT - Policy initializing/deleted or is unsigned
|
||||
*/
|
||||
static ssize_t read_pkcs7(struct file *f, char __user *data,
|
||||
size_t len, loff_t *offset)
|
||||
{
|
||||
const struct ipe_policy *p = NULL;
|
||||
struct inode *root = NULL;
|
||||
int rc = 0;
|
||||
|
||||
root = d_inode(f->f_path.dentry->d_parent);
|
||||
|
||||
inode_lock_shared(root);
|
||||
p = (struct ipe_policy *)root->i_private;
|
||||
if (!p) {
|
||||
rc = -ENOENT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!p->pkcs7) {
|
||||
rc = -ENOENT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
rc = simple_read_from_buffer(data, len, offset, p->pkcs7, p->pkcs7len);
|
||||
|
||||
out:
|
||||
inode_unlock_shared(root);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* read_policy() - Read handler for "ipe/policies/$name/policy".
|
||||
* @f: Supplies a file structure representing the securityfs node.
|
||||
* @data: Supplies a buffer passed to the write syscall.
|
||||
* @len: Supplies the length of @data.
|
||||
* @offset: unused.
|
||||
*
|
||||
* @data will be populated with the plain-text version of the policy
|
||||
* on success.
|
||||
*
|
||||
* Return:
|
||||
* * Length of buffer written - Success
|
||||
* * %-ENOENT - Policy initializing/deleted
|
||||
*/
|
||||
static ssize_t read_policy(struct file *f, char __user *data,
|
||||
size_t len, loff_t *offset)
|
||||
{
|
||||
const struct ipe_policy *p = NULL;
|
||||
struct inode *root = NULL;
|
||||
int rc = 0;
|
||||
|
||||
root = d_inode(f->f_path.dentry->d_parent);
|
||||
|
||||
inode_lock_shared(root);
|
||||
p = (struct ipe_policy *)root->i_private;
|
||||
if (!p) {
|
||||
rc = -ENOENT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
rc = simple_read_from_buffer(data, len, offset, p->text, p->textlen);
|
||||
|
||||
out:
|
||||
inode_unlock_shared(root);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* read_name() - Read handler for "ipe/policies/$name/name".
|
||||
* @f: Supplies a file structure representing the securityfs node.
|
||||
* @data: Supplies a buffer passed to the write syscall.
|
||||
* @len: Supplies the length of @data.
|
||||
* @offset: unused.
|
||||
*
|
||||
* @data will be populated with the policy_name attribute on success.
|
||||
*
|
||||
* Return:
|
||||
* * Length of buffer written - Success
|
||||
* * %-ENOENT - Policy initializing/deleted
|
||||
*/
|
||||
static ssize_t read_name(struct file *f, char __user *data,
|
||||
size_t len, loff_t *offset)
|
||||
{
|
||||
const struct ipe_policy *p = NULL;
|
||||
struct inode *root = NULL;
|
||||
int rc = 0;
|
||||
|
||||
root = d_inode(f->f_path.dentry->d_parent);
|
||||
|
||||
inode_lock_shared(root);
|
||||
p = (struct ipe_policy *)root->i_private;
|
||||
if (!p) {
|
||||
rc = -ENOENT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
rc = simple_read_from_buffer(data, len, offset, p->parsed->name,
|
||||
strlen(p->parsed->name));
|
||||
|
||||
out:
|
||||
inode_unlock_shared(root);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* read_version() - Read handler for "ipe/policies/$name/version".
|
||||
* @f: Supplies a file structure representing the securityfs node.
|
||||
* @data: Supplies a buffer passed to the write syscall.
|
||||
* @len: Supplies the length of @data.
|
||||
* @offset: unused.
|
||||
*
|
||||
* @data will be populated with the version string on success.
|
||||
*
|
||||
* Return:
|
||||
* * Length of buffer written - Success
|
||||
* * %-ENOENT - Policy initializing/deleted
|
||||
*/
|
||||
static ssize_t read_version(struct file *f, char __user *data,
|
||||
size_t len, loff_t *offset)
|
||||
{
|
||||
char buffer[MAX_VERSION_SIZE] = { 0 };
|
||||
const struct ipe_policy *p = NULL;
|
||||
struct inode *root = NULL;
|
||||
size_t strsize = 0;
|
||||
ssize_t rc = 0;
|
||||
|
||||
root = d_inode(f->f_path.dentry->d_parent);
|
||||
|
||||
inode_lock_shared(root);
|
||||
p = (struct ipe_policy *)root->i_private;
|
||||
if (!p) {
|
||||
rc = -ENOENT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
strsize = scnprintf(buffer, ARRAY_SIZE(buffer), "%hu.%hu.%hu",
|
||||
p->parsed->version.major, p->parsed->version.minor,
|
||||
p->parsed->version.rev);
|
||||
|
||||
rc = simple_read_from_buffer(data, len, offset, buffer, strsize);
|
||||
|
||||
out:
|
||||
inode_unlock_shared(root);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* setactive() - Write handler for "ipe/policies/$name/active".
|
||||
* @f: Supplies a file structure representing the securityfs node.
|
||||
* @data: Supplies a buffer passed to the write syscall.
|
||||
* @len: Supplies the length of @data.
|
||||
* @offset: unused.
|
||||
*
|
||||
* Return:
|
||||
* * Length of buffer written - Success
|
||||
* * %-EPERM - Insufficient permission
|
||||
* * %-EINVAL - Invalid input
|
||||
* * %-ENOENT - Policy initializing/deleted
|
||||
*/
|
||||
static ssize_t setactive(struct file *f, const char __user *data,
|
||||
size_t len, loff_t *offset)
|
||||
{
|
||||
const struct ipe_policy *p = NULL;
|
||||
struct inode *root = NULL;
|
||||
bool value = false;
|
||||
int rc = 0;
|
||||
|
||||
if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
rc = kstrtobool_from_user(data, len, &value);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
if (!value)
|
||||
return -EINVAL;
|
||||
|
||||
root = d_inode(f->f_path.dentry->d_parent);
|
||||
inode_lock(root);
|
||||
|
||||
p = (struct ipe_policy *)root->i_private;
|
||||
if (!p) {
|
||||
rc = -ENOENT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
rc = ipe_set_active_pol(p);
|
||||
|
||||
out:
|
||||
inode_unlock(root);
|
||||
return (rc < 0) ? rc : len;
|
||||
}
|
||||
|
||||
/**
|
||||
* getactive() - Read handler for "ipe/policies/$name/active".
|
||||
* @f: Supplies a file structure representing the securityfs node.
|
||||
* @data: Supplies a buffer passed to the write syscall.
|
||||
* @len: Supplies the length of @data.
|
||||
* @offset: unused.
|
||||
*
|
||||
* @data will be populated with the 1 or 0 depending on if the
|
||||
* corresponding policy is active.
|
||||
*
|
||||
* Return:
|
||||
* * Length of buffer written - Success
|
||||
* * %-ENOENT - Policy initializing/deleted
|
||||
*/
|
||||
static ssize_t getactive(struct file *f, char __user *data,
|
||||
size_t len, loff_t *offset)
|
||||
{
|
||||
const struct ipe_policy *p = NULL;
|
||||
struct inode *root = NULL;
|
||||
const char *str;
|
||||
int rc = 0;
|
||||
|
||||
root = d_inode(f->f_path.dentry->d_parent);
|
||||
|
||||
inode_lock_shared(root);
|
||||
p = (struct ipe_policy *)root->i_private;
|
||||
if (!p) {
|
||||
inode_unlock_shared(root);
|
||||
return -ENOENT;
|
||||
}
|
||||
inode_unlock_shared(root);
|
||||
|
||||
str = (p == rcu_access_pointer(ipe_active_policy)) ? "1" : "0";
|
||||
rc = simple_read_from_buffer(data, len, offset, str, 1);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* update_policy() - Write handler for "ipe/policies/$name/update".
|
||||
* @f: Supplies a file structure representing the securityfs node.
|
||||
* @data: Supplies a buffer passed to the write syscall.
|
||||
* @len: Supplies the length of @data.
|
||||
* @offset: unused.
|
||||
*
|
||||
* On success this updates the policy represented by $name,
|
||||
* in-place.
|
||||
*
|
||||
* Return: Length of buffer written on success. If an error occurs,
|
||||
* the function will return the -errno.
|
||||
*/
|
||||
static ssize_t update_policy(struct file *f, const char __user *data,
|
||||
size_t len, loff_t *offset)
|
||||
{
|
||||
struct inode *root = NULL;
|
||||
char *copy = NULL;
|
||||
int rc = 0;
|
||||
|
||||
if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
copy = memdup_user(data, len);
|
||||
if (IS_ERR(copy))
|
||||
return PTR_ERR(copy);
|
||||
|
||||
root = d_inode(f->f_path.dentry->d_parent);
|
||||
inode_lock(root);
|
||||
rc = ipe_update_policy(root, NULL, 0, copy, len);
|
||||
inode_unlock(root);
|
||||
|
||||
kfree(copy);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* delete_policy() - write handler for "ipe/policies/$name/delete".
|
||||
* @f: Supplies a file structure representing the securityfs node.
|
||||
* @data: Supplies a buffer passed to the write syscall.
|
||||
* @len: Supplies the length of @data.
|
||||
* @offset: unused.
|
||||
*
|
||||
* On success this deletes the policy represented by $name.
|
||||
*
|
||||
* Return:
|
||||
* * Length of buffer written - Success
|
||||
* * %-EPERM - Insufficient permission/deleting active policy
|
||||
* * %-EINVAL - Invalid input
|
||||
* * %-ENOENT - Policy initializing/deleted
|
||||
*/
|
||||
static ssize_t delete_policy(struct file *f, const char __user *data,
|
||||
size_t len, loff_t *offset)
|
||||
{
|
||||
struct ipe_policy *ap = NULL;
|
||||
struct ipe_policy *p = NULL;
|
||||
struct inode *root = NULL;
|
||||
bool value = false;
|
||||
int rc = 0;
|
||||
|
||||
if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
rc = kstrtobool_from_user(data, len, &value);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
if (!value)
|
||||
return -EINVAL;
|
||||
|
||||
root = d_inode(f->f_path.dentry->d_parent);
|
||||
inode_lock(root);
|
||||
p = (struct ipe_policy *)root->i_private;
|
||||
if (!p) {
|
||||
inode_unlock(root);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
mutex_lock(&ipe_policy_lock);
|
||||
ap = rcu_dereference_protected(ipe_active_policy,
|
||||
lockdep_is_held(&ipe_policy_lock));
|
||||
if (p == ap) {
|
||||
mutex_unlock(&ipe_policy_lock);
|
||||
inode_unlock(root);
|
||||
return -EPERM;
|
||||
}
|
||||
mutex_unlock(&ipe_policy_lock);
|
||||
|
||||
root->i_private = NULL;
|
||||
inode_unlock(root);
|
||||
|
||||
synchronize_rcu();
|
||||
ipe_free_policy(p);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static const struct file_operations content_fops = {
|
||||
.read = read_policy,
|
||||
};
|
||||
|
||||
static const struct file_operations pkcs7_fops = {
|
||||
.read = read_pkcs7,
|
||||
};
|
||||
|
||||
static const struct file_operations name_fops = {
|
||||
.read = read_name,
|
||||
};
|
||||
|
||||
static const struct file_operations ver_fops = {
|
||||
.read = read_version,
|
||||
};
|
||||
|
||||
static const struct file_operations active_fops = {
|
||||
.write = setactive,
|
||||
.read = getactive,
|
||||
};
|
||||
|
||||
static const struct file_operations update_fops = {
|
||||
.write = update_policy,
|
||||
};
|
||||
|
||||
static const struct file_operations delete_fops = {
|
||||
.write = delete_policy,
|
||||
};
|
||||
|
||||
/**
|
||||
* policy_subdir - files under a policy subdirectory
|
||||
*/
|
||||
static const struct ipefs_file policy_subdir[] = {
|
||||
{ "pkcs7", 0444, &pkcs7_fops },
|
||||
{ "policy", 0444, &content_fops },
|
||||
{ "name", 0444, &name_fops },
|
||||
{ "version", 0444, &ver_fops },
|
||||
{ "active", 0600, &active_fops },
|
||||
{ "update", 0200, &update_fops },
|
||||
{ "delete", 0200, &delete_fops },
|
||||
};
|
||||
|
||||
/**
|
||||
* ipe_del_policyfs_node() - Delete a securityfs entry for @p.
|
||||
* @p: Supplies a pointer to the policy to delete a securityfs entry for.
|
||||
*/
|
||||
void ipe_del_policyfs_node(struct ipe_policy *p)
|
||||
{
|
||||
securityfs_recursive_remove(p->policyfs);
|
||||
p->policyfs = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* ipe_new_policyfs_node() - Create a securityfs entry for @p.
|
||||
* @p: Supplies a pointer to the policy to create a securityfs entry for.
|
||||
*
|
||||
* Return: %0 on success. If an error occurs, the function will return
|
||||
* the -errno.
|
||||
*/
|
||||
int ipe_new_policyfs_node(struct ipe_policy *p)
|
||||
{
|
||||
const struct ipefs_file *f = NULL;
|
||||
struct dentry *policyfs = NULL;
|
||||
struct inode *root = NULL;
|
||||
struct dentry *d = NULL;
|
||||
size_t i = 0;
|
||||
int rc = 0;
|
||||
|
||||
if (p->policyfs)
|
||||
return 0;
|
||||
|
||||
policyfs = securityfs_create_dir(p->parsed->name, policy_root);
|
||||
if (IS_ERR(policyfs))
|
||||
return PTR_ERR(policyfs);
|
||||
|
||||
root = d_inode(policyfs);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(policy_subdir); ++i) {
|
||||
f = &policy_subdir[i];
|
||||
|
||||
d = securityfs_create_file(f->name, f->access, policyfs,
|
||||
NULL, f->fops);
|
||||
if (IS_ERR(d)) {
|
||||
rc = PTR_ERR(d);
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
inode_lock(root);
|
||||
p->policyfs = policyfs;
|
||||
root->i_private = p;
|
||||
inode_unlock(root);
|
||||
|
||||
return 0;
|
||||
err:
|
||||
securityfs_recursive_remove(policyfs);
|
||||
return rc;
|
||||
}
|
||||
@@ -0,0 +1,559 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/parser.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/ctype.h>
|
||||
|
||||
#include "policy.h"
|
||||
#include "policy_parser.h"
|
||||
#include "digest.h"
|
||||
|
||||
#define START_COMMENT '#'
|
||||
#define IPE_POLICY_DELIM " \t"
|
||||
#define IPE_LINE_DELIM "\n\r"
|
||||
|
||||
/**
|
||||
* new_parsed_policy() - Allocate and initialize a parsed policy.
|
||||
*
|
||||
* Return:
|
||||
* * a pointer to the ipe_parsed_policy structure - Success
|
||||
* * %-ENOMEM - Out of memory (OOM)
|
||||
*/
|
||||
static struct ipe_parsed_policy *new_parsed_policy(void)
|
||||
{
|
||||
struct ipe_parsed_policy *p = NULL;
|
||||
struct ipe_op_table *t = NULL;
|
||||
size_t i = 0;
|
||||
|
||||
p = kzalloc(sizeof(*p), GFP_KERNEL);
|
||||
if (!p)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
p->global_default_action = IPE_ACTION_INVALID;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(p->rules); ++i) {
|
||||
t = &p->rules[i];
|
||||
|
||||
t->default_action = IPE_ACTION_INVALID;
|
||||
INIT_LIST_HEAD(&t->rules);
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* remove_comment() - Truncate all chars following START_COMMENT in a string.
|
||||
*
|
||||
* @line: Supplies a policy line string for preprocessing.
|
||||
*/
|
||||
static void remove_comment(char *line)
|
||||
{
|
||||
line = strchr(line, START_COMMENT);
|
||||
|
||||
if (line)
|
||||
*line = '\0';
|
||||
}
|
||||
|
||||
/**
|
||||
* remove_trailing_spaces() - Truncate all trailing spaces in a string.
|
||||
*
|
||||
* @line: Supplies a policy line string for preprocessing.
|
||||
*
|
||||
* Return: The length of truncated string.
|
||||
*/
|
||||
static size_t remove_trailing_spaces(char *line)
|
||||
{
|
||||
size_t i = 0;
|
||||
|
||||
i = strlen(line);
|
||||
while (i > 0 && isspace(line[i - 1]))
|
||||
i--;
|
||||
|
||||
line[i] = '\0';
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* parse_version() - Parse policy version.
|
||||
* @ver: Supplies a version string to be parsed.
|
||||
* @p: Supplies the partial parsed policy.
|
||||
*
|
||||
* Return:
|
||||
* * %0 - Success
|
||||
* * %-EBADMSG - Version string is invalid
|
||||
* * %-ERANGE - Version number overflow
|
||||
* * %-EINVAL - Parsing error
|
||||
*/
|
||||
static int parse_version(char *ver, struct ipe_parsed_policy *p)
|
||||
{
|
||||
u16 *const cv[] = { &p->version.major, &p->version.minor, &p->version.rev };
|
||||
size_t sep_count = 0;
|
||||
char *token;
|
||||
int rc = 0;
|
||||
|
||||
while ((token = strsep(&ver, ".")) != NULL) {
|
||||
/* prevent overflow */
|
||||
if (sep_count >= ARRAY_SIZE(cv))
|
||||
return -EBADMSG;
|
||||
|
||||
rc = kstrtou16(token, 10, cv[sep_count]);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
++sep_count;
|
||||
}
|
||||
|
||||
/* prevent underflow */
|
||||
if (sep_count != ARRAY_SIZE(cv))
|
||||
return -EBADMSG;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
enum header_opt {
|
||||
IPE_HEADER_POLICY_NAME = 0,
|
||||
IPE_HEADER_POLICY_VERSION,
|
||||
__IPE_HEADER_MAX
|
||||
};
|
||||
|
||||
static const match_table_t header_tokens = {
|
||||
{IPE_HEADER_POLICY_NAME, "policy_name=%s"},
|
||||
{IPE_HEADER_POLICY_VERSION, "policy_version=%s"},
|
||||
{__IPE_HEADER_MAX, NULL}
|
||||
};
|
||||
|
||||
/**
|
||||
* parse_header() - Parse policy header information.
|
||||
* @line: Supplies header line to be parsed.
|
||||
* @p: Supplies the partial parsed policy.
|
||||
*
|
||||
* Return:
|
||||
* * %0 - Success
|
||||
* * %-EBADMSG - Header string is invalid
|
||||
* * %-ENOMEM - Out of memory (OOM)
|
||||
* * %-ERANGE - Version number overflow
|
||||
* * %-EINVAL - Version parsing error
|
||||
*/
|
||||
static int parse_header(char *line, struct ipe_parsed_policy *p)
|
||||
{
|
||||
substring_t args[MAX_OPT_ARGS];
|
||||
char *t, *ver = NULL;
|
||||
size_t idx = 0;
|
||||
int rc = 0;
|
||||
|
||||
while ((t = strsep(&line, IPE_POLICY_DELIM)) != NULL) {
|
||||
int token;
|
||||
|
||||
if (*t == '\0')
|
||||
continue;
|
||||
if (idx >= __IPE_HEADER_MAX) {
|
||||
rc = -EBADMSG;
|
||||
goto out;
|
||||
}
|
||||
|
||||
token = match_token(t, header_tokens, args);
|
||||
if (token != idx) {
|
||||
rc = -EBADMSG;
|
||||
goto out;
|
||||
}
|
||||
|
||||
switch (token) {
|
||||
case IPE_HEADER_POLICY_NAME:
|
||||
p->name = match_strdup(&args[0]);
|
||||
if (!p->name)
|
||||
rc = -ENOMEM;
|
||||
break;
|
||||
case IPE_HEADER_POLICY_VERSION:
|
||||
ver = match_strdup(&args[0]);
|
||||
if (!ver) {
|
||||
rc = -ENOMEM;
|
||||
break;
|
||||
}
|
||||
rc = parse_version(ver, p);
|
||||
break;
|
||||
default:
|
||||
rc = -EBADMSG;
|
||||
}
|
||||
if (rc)
|
||||
goto out;
|
||||
++idx;
|
||||
}
|
||||
|
||||
if (idx != __IPE_HEADER_MAX)
|
||||
rc = -EBADMSG;
|
||||
|
||||
out:
|
||||
kfree(ver);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* token_default() - Determine if the given token is "DEFAULT".
|
||||
* @token: Supplies the token string to be compared.
|
||||
*
|
||||
* Return:
|
||||
* * %false - The token is not "DEFAULT"
|
||||
* * %true - The token is "DEFAULT"
|
||||
*/
|
||||
static bool token_default(char *token)
|
||||
{
|
||||
return !strcmp(token, "DEFAULT");
|
||||
}
|
||||
|
||||
/**
|
||||
* free_rule() - Free the supplied ipe_rule struct.
|
||||
* @r: Supplies the ipe_rule struct to be freed.
|
||||
*
|
||||
* Free a ipe_rule struct @r. Note @r must be removed from any lists before
|
||||
* calling this function.
|
||||
*/
|
||||
static void free_rule(struct ipe_rule *r)
|
||||
{
|
||||
struct ipe_prop *p, *t;
|
||||
|
||||
if (IS_ERR_OR_NULL(r))
|
||||
return;
|
||||
|
||||
list_for_each_entry_safe(p, t, &r->props, next) {
|
||||
list_del(&p->next);
|
||||
ipe_digest_free(p->value);
|
||||
kfree(p);
|
||||
}
|
||||
|
||||
kfree(r);
|
||||
}
|
||||
|
||||
static const match_table_t operation_tokens = {
|
||||
{IPE_OP_EXEC, "op=EXECUTE"},
|
||||
{IPE_OP_FIRMWARE, "op=FIRMWARE"},
|
||||
{IPE_OP_KERNEL_MODULE, "op=KMODULE"},
|
||||
{IPE_OP_KEXEC_IMAGE, "op=KEXEC_IMAGE"},
|
||||
{IPE_OP_KEXEC_INITRAMFS, "op=KEXEC_INITRAMFS"},
|
||||
{IPE_OP_POLICY, "op=POLICY"},
|
||||
{IPE_OP_X509, "op=X509_CERT"},
|
||||
{IPE_OP_INVALID, NULL}
|
||||
};
|
||||
|
||||
/**
|
||||
* parse_operation() - Parse the operation type given a token string.
|
||||
* @t: Supplies the token string to be parsed.
|
||||
*
|
||||
* Return: The parsed operation type.
|
||||
*/
|
||||
static enum ipe_op_type parse_operation(char *t)
|
||||
{
|
||||
substring_t args[MAX_OPT_ARGS];
|
||||
|
||||
return match_token(t, operation_tokens, args);
|
||||
}
|
||||
|
||||
static const match_table_t action_tokens = {
|
||||
{IPE_ACTION_ALLOW, "action=ALLOW"},
|
||||
{IPE_ACTION_DENY, "action=DENY"},
|
||||
{IPE_ACTION_INVALID, NULL}
|
||||
};
|
||||
|
||||
/**
|
||||
* parse_action() - Parse the action type given a token string.
|
||||
* @t: Supplies the token string to be parsed.
|
||||
*
|
||||
* Return: The parsed action type.
|
||||
*/
|
||||
static enum ipe_action_type parse_action(char *t)
|
||||
{
|
||||
substring_t args[MAX_OPT_ARGS];
|
||||
|
||||
return match_token(t, action_tokens, args);
|
||||
}
|
||||
|
||||
static const match_table_t property_tokens = {
|
||||
{IPE_PROP_BOOT_VERIFIED_FALSE, "boot_verified=FALSE"},
|
||||
{IPE_PROP_BOOT_VERIFIED_TRUE, "boot_verified=TRUE"},
|
||||
{IPE_PROP_DMV_ROOTHASH, "dmverity_roothash=%s"},
|
||||
{IPE_PROP_DMV_SIG_FALSE, "dmverity_signature=FALSE"},
|
||||
{IPE_PROP_DMV_SIG_TRUE, "dmverity_signature=TRUE"},
|
||||
{IPE_PROP_FSV_DIGEST, "fsverity_digest=%s"},
|
||||
{IPE_PROP_FSV_SIG_FALSE, "fsverity_signature=FALSE"},
|
||||
{IPE_PROP_FSV_SIG_TRUE, "fsverity_signature=TRUE"},
|
||||
{IPE_PROP_INVALID, NULL}
|
||||
};
|
||||
|
||||
/**
|
||||
* parse_property() - Parse a rule property given a token string.
|
||||
* @t: Supplies the token string to be parsed.
|
||||
* @r: Supplies the ipe_rule the parsed property will be associated with.
|
||||
*
|
||||
* This function parses and associates a property with an IPE rule based
|
||||
* on a token string.
|
||||
*
|
||||
* Return:
|
||||
* * %0 - Success
|
||||
* * %-ENOMEM - Out of memory (OOM)
|
||||
* * %-EBADMSG - The supplied token cannot be parsed
|
||||
*/
|
||||
static int parse_property(char *t, struct ipe_rule *r)
|
||||
{
|
||||
substring_t args[MAX_OPT_ARGS];
|
||||
struct ipe_prop *p = NULL;
|
||||
int rc = 0;
|
||||
int token;
|
||||
char *dup = NULL;
|
||||
|
||||
p = kzalloc(sizeof(*p), GFP_KERNEL);
|
||||
if (!p)
|
||||
return -ENOMEM;
|
||||
|
||||
token = match_token(t, property_tokens, args);
|
||||
|
||||
switch (token) {
|
||||
case IPE_PROP_DMV_ROOTHASH:
|
||||
case IPE_PROP_FSV_DIGEST:
|
||||
dup = match_strdup(&args[0]);
|
||||
if (!dup) {
|
||||
rc = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
p->value = ipe_digest_parse(dup);
|
||||
if (IS_ERR(p->value)) {
|
||||
rc = PTR_ERR(p->value);
|
||||
goto err;
|
||||
}
|
||||
fallthrough;
|
||||
case IPE_PROP_BOOT_VERIFIED_FALSE:
|
||||
case IPE_PROP_BOOT_VERIFIED_TRUE:
|
||||
case IPE_PROP_DMV_SIG_FALSE:
|
||||
case IPE_PROP_DMV_SIG_TRUE:
|
||||
case IPE_PROP_FSV_SIG_FALSE:
|
||||
case IPE_PROP_FSV_SIG_TRUE:
|
||||
p->type = token;
|
||||
break;
|
||||
default:
|
||||
rc = -EBADMSG;
|
||||
break;
|
||||
}
|
||||
if (rc)
|
||||
goto err;
|
||||
list_add_tail(&p->next, &r->props);
|
||||
|
||||
out:
|
||||
kfree(dup);
|
||||
return rc;
|
||||
err:
|
||||
kfree(p);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/**
|
||||
* parse_rule() - parse a policy rule line.
|
||||
* @line: Supplies rule line to be parsed.
|
||||
* @p: Supplies the partial parsed policy.
|
||||
*
|
||||
* Return:
|
||||
* * 0 - Success
|
||||
* * %-ENOMEM - Out of memory (OOM)
|
||||
* * %-EBADMSG - Policy syntax error
|
||||
*/
|
||||
static int parse_rule(char *line, struct ipe_parsed_policy *p)
|
||||
{
|
||||
enum ipe_action_type action = IPE_ACTION_INVALID;
|
||||
enum ipe_op_type op = IPE_OP_INVALID;
|
||||
bool is_default_rule = false;
|
||||
struct ipe_rule *r = NULL;
|
||||
bool first_token = true;
|
||||
bool op_parsed = false;
|
||||
int rc = 0;
|
||||
char *t;
|
||||
|
||||
if (IS_ERR_OR_NULL(line))
|
||||
return -EBADMSG;
|
||||
|
||||
r = kzalloc(sizeof(*r), GFP_KERNEL);
|
||||
if (!r)
|
||||
return -ENOMEM;
|
||||
|
||||
INIT_LIST_HEAD(&r->next);
|
||||
INIT_LIST_HEAD(&r->props);
|
||||
|
||||
while (t = strsep(&line, IPE_POLICY_DELIM), line) {
|
||||
if (*t == '\0')
|
||||
continue;
|
||||
if (first_token && token_default(t)) {
|
||||
is_default_rule = true;
|
||||
} else {
|
||||
if (!op_parsed) {
|
||||
op = parse_operation(t);
|
||||
if (op == IPE_OP_INVALID)
|
||||
rc = -EBADMSG;
|
||||
else
|
||||
op_parsed = true;
|
||||
} else {
|
||||
rc = parse_property(t, r);
|
||||
}
|
||||
}
|
||||
|
||||
if (rc)
|
||||
goto err;
|
||||
first_token = false;
|
||||
}
|
||||
|
||||
action = parse_action(t);
|
||||
if (action == IPE_ACTION_INVALID) {
|
||||
rc = -EBADMSG;
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (is_default_rule) {
|
||||
if (!list_empty(&r->props)) {
|
||||
rc = -EBADMSG;
|
||||
} else if (op == IPE_OP_INVALID) {
|
||||
if (p->global_default_action != IPE_ACTION_INVALID)
|
||||
rc = -EBADMSG;
|
||||
else
|
||||
p->global_default_action = action;
|
||||
} else {
|
||||
if (p->rules[op].default_action != IPE_ACTION_INVALID)
|
||||
rc = -EBADMSG;
|
||||
else
|
||||
p->rules[op].default_action = action;
|
||||
}
|
||||
} else if (op != IPE_OP_INVALID && action != IPE_ACTION_INVALID) {
|
||||
r->op = op;
|
||||
r->action = action;
|
||||
} else {
|
||||
rc = -EBADMSG;
|
||||
}
|
||||
|
||||
if (rc)
|
||||
goto err;
|
||||
if (!is_default_rule)
|
||||
list_add_tail(&r->next, &p->rules[op].rules);
|
||||
else
|
||||
free_rule(r);
|
||||
|
||||
return rc;
|
||||
err:
|
||||
free_rule(r);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* ipe_free_parsed_policy() - free a parsed policy structure.
|
||||
* @p: Supplies the parsed policy.
|
||||
*/
|
||||
void ipe_free_parsed_policy(struct ipe_parsed_policy *p)
|
||||
{
|
||||
struct ipe_rule *pp, *t;
|
||||
size_t i = 0;
|
||||
|
||||
if (IS_ERR_OR_NULL(p))
|
||||
return;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(p->rules); ++i)
|
||||
list_for_each_entry_safe(pp, t, &p->rules[i].rules, next) {
|
||||
list_del(&pp->next);
|
||||
free_rule(pp);
|
||||
}
|
||||
|
||||
kfree(p->name);
|
||||
kfree(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* validate_policy() - validate a parsed policy.
|
||||
* @p: Supplies the fully parsed policy.
|
||||
*
|
||||
* Given a policy structure that was just parsed, validate that all
|
||||
* operations have their default rules or a global default rule is set.
|
||||
*
|
||||
* Return:
|
||||
* * %0 - Success
|
||||
* * %-EBADMSG - Policy is invalid
|
||||
*/
|
||||
static int validate_policy(const struct ipe_parsed_policy *p)
|
||||
{
|
||||
size_t i = 0;
|
||||
|
||||
if (p->global_default_action != IPE_ACTION_INVALID)
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(p->rules); ++i) {
|
||||
if (p->rules[i].default_action == IPE_ACTION_INVALID)
|
||||
return -EBADMSG;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* ipe_parse_policy() - Given a string, parse the string into an IPE policy.
|
||||
* @p: partially filled ipe_policy structure to populate with the result.
|
||||
* it must have text and textlen set.
|
||||
*
|
||||
* Return:
|
||||
* * %0 - Success
|
||||
* * %-EBADMSG - Policy is invalid
|
||||
* * %-ENOMEM - Out of Memory
|
||||
* * %-ERANGE - Policy version number overflow
|
||||
* * %-EINVAL - Policy version parsing error
|
||||
*/
|
||||
int ipe_parse_policy(struct ipe_policy *p)
|
||||
{
|
||||
struct ipe_parsed_policy *pp = NULL;
|
||||
char *policy = NULL, *dup = NULL;
|
||||
bool header_parsed = false;
|
||||
char *line = NULL;
|
||||
size_t len;
|
||||
int rc = 0;
|
||||
|
||||
if (!p->textlen)
|
||||
return -EBADMSG;
|
||||
|
||||
policy = kmemdup_nul(p->text, p->textlen, GFP_KERNEL);
|
||||
if (!policy)
|
||||
return -ENOMEM;
|
||||
dup = policy;
|
||||
|
||||
pp = new_parsed_policy();
|
||||
if (IS_ERR(pp)) {
|
||||
rc = PTR_ERR(pp);
|
||||
goto out;
|
||||
}
|
||||
|
||||
while ((line = strsep(&policy, IPE_LINE_DELIM)) != NULL) {
|
||||
remove_comment(line);
|
||||
len = remove_trailing_spaces(line);
|
||||
if (!len)
|
||||
continue;
|
||||
|
||||
if (!header_parsed) {
|
||||
rc = parse_header(line, pp);
|
||||
if (rc)
|
||||
goto err;
|
||||
header_parsed = true;
|
||||
} else {
|
||||
rc = parse_rule(line, pp);
|
||||
if (rc)
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
if (!header_parsed || validate_policy(pp)) {
|
||||
rc = -EBADMSG;
|
||||
goto err;
|
||||
}
|
||||
|
||||
p->parsed = pp;
|
||||
|
||||
out:
|
||||
kfree(dup);
|
||||
return rc;
|
||||
err:
|
||||
ipe_free_parsed_policy(pp);
|
||||
goto out;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
#ifndef _IPE_POLICY_PARSER_H
|
||||
#define _IPE_POLICY_PARSER_H
|
||||
|
||||
int ipe_parse_policy(struct ipe_policy *p);
|
||||
void ipe_free_parsed_policy(struct ipe_parsed_policy *p);
|
||||
|
||||
#endif /* _IPE_POLICY_PARSER_H */
|
||||
@@ -0,0 +1,296 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/list.h>
|
||||
#include <kunit/test.h>
|
||||
#include "policy.h"
|
||||
struct policy_case {
|
||||
const char *const policy;
|
||||
int errno;
|
||||
const char *const desc;
|
||||
};
|
||||
|
||||
static const struct policy_case policy_cases[] = {
|
||||
{
|
||||
"policy_name=allowall policy_version=0.0.0\n"
|
||||
"DEFAULT action=ALLOW",
|
||||
0,
|
||||
"basic",
|
||||
},
|
||||
{
|
||||
"policy_name=trailing_comment policy_version=152.0.0 #This is comment\n"
|
||||
"DEFAULT action=ALLOW",
|
||||
0,
|
||||
"trailing comment",
|
||||
},
|
||||
{
|
||||
"policy_name=allowallnewline policy_version=0.2.0\n"
|
||||
"DEFAULT action=ALLOW\n"
|
||||
"\n",
|
||||
0,
|
||||
"trailing newline",
|
||||
},
|
||||
{
|
||||
"policy_name=carriagereturnlinefeed policy_version=0.0.1\n"
|
||||
"DEFAULT action=ALLOW\n"
|
||||
"\r\n",
|
||||
0,
|
||||
"clrf newline",
|
||||
},
|
||||
{
|
||||
"policy_name=whitespace policy_version=0.0.0\n"
|
||||
"DEFAULT\taction=ALLOW\n"
|
||||
" \t DEFAULT \t op=EXECUTE action=DENY\n"
|
||||
"op=EXECUTE boot_verified=TRUE action=ALLOW\n"
|
||||
"# this is a\tcomment\t\t\t\t\n"
|
||||
"DEFAULT \t op=KMODULE\t\t\t action=DENY\r\n"
|
||||
"op=KMODULE boot_verified=TRUE action=ALLOW\n",
|
||||
0,
|
||||
"various whitespaces and nested default",
|
||||
},
|
||||
{
|
||||
"policy_name=boot_verified policy_version=-1236.0.0\n"
|
||||
"DEFAULT\taction=ALLOW\n",
|
||||
-EINVAL,
|
||||
"negative version",
|
||||
},
|
||||
{
|
||||
"policy_name=$@!*&^%%\\:;{}() policy_version=0.0.0\n"
|
||||
"DEFAULT action=ALLOW",
|
||||
0,
|
||||
"special characters",
|
||||
},
|
||||
{
|
||||
"policy_name=test policy_version=999999.0.0\n"
|
||||
"DEFAULT action=ALLOW",
|
||||
-ERANGE,
|
||||
"overflow version",
|
||||
},
|
||||
{
|
||||
"policy_name=test policy_version=255.0\n"
|
||||
"DEFAULT action=ALLOW",
|
||||
-EBADMSG,
|
||||
"incomplete version",
|
||||
},
|
||||
{
|
||||
"policy_name=test policy_version=111.0.0.0\n"
|
||||
"DEFAULT action=ALLOW",
|
||||
-EBADMSG,
|
||||
"extra version",
|
||||
},
|
||||
{
|
||||
"",
|
||||
-EBADMSG,
|
||||
"0-length policy",
|
||||
},
|
||||
{
|
||||
"policy_name=test\0policy_version=0.0.0\n"
|
||||
"DEFAULT action=ALLOW",
|
||||
-EBADMSG,
|
||||
"random null in header",
|
||||
},
|
||||
{
|
||||
"policy_name=test policy_version=0.0.0\n"
|
||||
"\0DEFAULT action=ALLOW",
|
||||
-EBADMSG,
|
||||
"incomplete policy from NULL",
|
||||
},
|
||||
{
|
||||
"policy_name=test policy_version=0.0.0\n"
|
||||
"DEFAULT action=DENY\n\0"
|
||||
"op=EXECUTE dmverity_signature=TRUE action=ALLOW\n",
|
||||
0,
|
||||
"NULL truncates policy",
|
||||
},
|
||||
{
|
||||
"policy_name=test policy_version=0.0.0\n"
|
||||
"DEFAULT action=ALLOW\n"
|
||||
"op=EXECUTE dmverity_signature=abc action=ALLOW",
|
||||
-EBADMSG,
|
||||
"invalid property type",
|
||||
},
|
||||
{
|
||||
"DEFAULT action=ALLOW",
|
||||
-EBADMSG,
|
||||
"missing policy header",
|
||||
},
|
||||
{
|
||||
"policy_name=test policy_version=0.0.0\n",
|
||||
-EBADMSG,
|
||||
"missing default definition",
|
||||
},
|
||||
{
|
||||
"policy_name=test policy_version=0.0.0\n"
|
||||
"DEFAULT action=ALLOW\n"
|
||||
"dmverity_signature=TRUE op=EXECUTE action=ALLOW",
|
||||
-EBADMSG,
|
||||
"invalid rule ordering"
|
||||
},
|
||||
{
|
||||
"policy_name=test policy_version=0.0.0\n"
|
||||
"DEFAULT action=ALLOW\n"
|
||||
"action=ALLOW op=EXECUTE dmverity_signature=TRUE",
|
||||
-EBADMSG,
|
||||
"invalid rule ordering (2)",
|
||||
},
|
||||
{
|
||||
"policy_name=test policy_version=0.0\n"
|
||||
"DEFAULT action=ALLOW\n"
|
||||
"op=EXECUTE dmverity_signature=TRUE action=ALLOW",
|
||||
-EBADMSG,
|
||||
"invalid version",
|
||||
},
|
||||
{
|
||||
"policy_name=test policy_version=0.0.0\n"
|
||||
"DEFAULT action=ALLOW\n"
|
||||
"op=UNKNOWN dmverity_signature=TRUE action=ALLOW",
|
||||
-EBADMSG,
|
||||
"unknown operation",
|
||||
},
|
||||
{
|
||||
"policy_name=asdvpolicy_version=0.0.0\n"
|
||||
"DEFAULT action=ALLOW\n",
|
||||
-EBADMSG,
|
||||
"missing space after policy name",
|
||||
},
|
||||
{
|
||||
"policy_name=test\xFF\xEF policy_version=0.0.0\n"
|
||||
"DEFAULT action=ALLOW\n"
|
||||
"op=EXECUTE dmverity_signature=TRUE action=ALLOW",
|
||||
0,
|
||||
"expanded ascii",
|
||||
},
|
||||
{
|
||||
"policy_name=test\xFF\xEF policy_version=0.0.0\n"
|
||||
"DEFAULT action=ALLOW\n"
|
||||
"op=EXECUTE dmverity_roothash=GOOD_DOG action=ALLOW",
|
||||
-EBADMSG,
|
||||
"invalid property value (2)",
|
||||
},
|
||||
{
|
||||
"policy_name=test policy_version=0.0.0\n"
|
||||
"policy_name=test policy_version=0.1.0\n"
|
||||
"DEFAULT action=ALLOW",
|
||||
-EBADMSG,
|
||||
"double header"
|
||||
},
|
||||
{
|
||||
"policy_name=test policy_version=0.0.0\n"
|
||||
"DEFAULT action=ALLOW\n"
|
||||
"DEFAULT action=ALLOW\n",
|
||||
-EBADMSG,
|
||||
"double default"
|
||||
},
|
||||
{
|
||||
"policy_name=test policy_version=0.0.0\n"
|
||||
"DEFAULT action=ALLOW\n"
|
||||
"DEFAULT op=EXECUTE action=DENY\n"
|
||||
"DEFAULT op=EXECUTE action=ALLOW\n",
|
||||
-EBADMSG,
|
||||
"double operation default"
|
||||
},
|
||||
{
|
||||
"policy_name=test policy_version=0.0.0\n"
|
||||
"DEFAULT action=ALLOW\n"
|
||||
"DEFAULT op=EXECUTE action=DEN\n",
|
||||
-EBADMSG,
|
||||
"invalid action value"
|
||||
},
|
||||
{
|
||||
"policy_name=test policy_version=0.0.0\n"
|
||||
"DEFAULT action=ALLOW\n"
|
||||
"DEFAULT op=EXECUTE action\n",
|
||||
-EBADMSG,
|
||||
"invalid action value (2)"
|
||||
},
|
||||
{
|
||||
"policy_name=test policy_version=0.0.0\n"
|
||||
"DEFAULT action=ALLOW\n"
|
||||
"UNKNOWN value=true\n",
|
||||
-EBADMSG,
|
||||
"unrecognized statement"
|
||||
},
|
||||
{
|
||||
"policy_name=test policy_version=0.0.0\n"
|
||||
"DEFAULT action=ALLOW\n"
|
||||
"op=EXECUTE dmverity_roothash=1c0d7ee1f8343b7fbe418378e8eb22c061d7dec7 action=DENY\n",
|
||||
-EBADMSG,
|
||||
"old-style digest"
|
||||
},
|
||||
{
|
||||
"policy_name=test policy_version=0.0.0\n"
|
||||
"DEFAULT action=ALLOW\n"
|
||||
"op=EXECUTE fsverity_digest=1c0d7ee1f8343b7fbe418378e8eb22c061d7dec7 action=DENY\n",
|
||||
-EBADMSG,
|
||||
"old-style digest"
|
||||
}
|
||||
};
|
||||
|
||||
static void pol_to_desc(const struct policy_case *c, char *desc)
|
||||
{
|
||||
strscpy(desc, c->desc, KUNIT_PARAM_DESC_SIZE);
|
||||
}
|
||||
|
||||
KUNIT_ARRAY_PARAM(ipe_policies, policy_cases, pol_to_desc);
|
||||
|
||||
/**
|
||||
* ipe_parser_unsigned_test - Test the parser by passing unsigned policies.
|
||||
* @test: Supplies a pointer to a kunit structure.
|
||||
*
|
||||
* This is called by the kunit harness. This test does not check the correctness
|
||||
* of the policy, but ensures that errors are handled correctly.
|
||||
*/
|
||||
static void ipe_parser_unsigned_test(struct kunit *test)
|
||||
{
|
||||
const struct policy_case *p = test->param_value;
|
||||
struct ipe_policy *pol;
|
||||
|
||||
pol = ipe_new_policy(p->policy, strlen(p->policy), NULL, 0);
|
||||
|
||||
if (p->errno) {
|
||||
KUNIT_EXPECT_EQ(test, PTR_ERR(pol), p->errno);
|
||||
return;
|
||||
}
|
||||
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pol);
|
||||
KUNIT_EXPECT_NOT_ERR_OR_NULL(test, pol->parsed);
|
||||
KUNIT_EXPECT_STREQ(test, pol->text, p->policy);
|
||||
KUNIT_EXPECT_PTR_EQ(test, NULL, pol->pkcs7);
|
||||
KUNIT_EXPECT_EQ(test, 0, pol->pkcs7len);
|
||||
|
||||
ipe_free_policy(pol);
|
||||
}
|
||||
|
||||
/**
|
||||
* ipe_parser_widestring_test - Ensure parser fail on a wide string policy.
|
||||
* @test: Supplies a pointer to a kunit structure.
|
||||
*
|
||||
* This is called by the kunit harness.
|
||||
*/
|
||||
static void ipe_parser_widestring_test(struct kunit *test)
|
||||
{
|
||||
const unsigned short policy[] = L"policy_name=Test policy_version=0.0.0\n"
|
||||
L"DEFAULT action=ALLOW";
|
||||
struct ipe_policy *pol = NULL;
|
||||
|
||||
pol = ipe_new_policy((const char *)policy, (ARRAY_SIZE(policy) - 1) * 2, NULL, 0);
|
||||
KUNIT_EXPECT_TRUE(test, IS_ERR_OR_NULL(pol));
|
||||
|
||||
ipe_free_policy(pol);
|
||||
}
|
||||
|
||||
static struct kunit_case ipe_parser_test_cases[] = {
|
||||
KUNIT_CASE_PARAM(ipe_parser_unsigned_test, ipe_policies_gen_params),
|
||||
KUNIT_CASE(ipe_parser_widestring_test),
|
||||
};
|
||||
|
||||
static struct kunit_suite ipe_parser_test_suite = {
|
||||
.name = "ipe-parser",
|
||||
.test_cases = ipe_parser_test_cases,
|
||||
};
|
||||
|
||||
kunit_test_suite(ipe_parser_test_suite);
|
||||
@@ -1207,13 +1207,16 @@ static int current_check_refer_path(struct dentry *const old_dentry,
|
||||
|
||||
/* Inode hooks */
|
||||
|
||||
static void hook_inode_free_security(struct inode *const inode)
|
||||
static void hook_inode_free_security_rcu(void *inode_security)
|
||||
{
|
||||
struct landlock_inode_security *inode_sec;
|
||||
|
||||
/*
|
||||
* All inodes must already have been untied from their object by
|
||||
* release_inode() or hook_sb_delete().
|
||||
*/
|
||||
WARN_ON_ONCE(landlock_inode(inode)->object);
|
||||
inode_sec = inode_security + landlock_blob_sizes.lbs_inode;
|
||||
WARN_ON_ONCE(inode_sec->object);
|
||||
}
|
||||
|
||||
/* Super-block hooks */
|
||||
@@ -1637,7 +1640,7 @@ static int hook_file_ioctl_compat(struct file *file, unsigned int cmd,
|
||||
}
|
||||
|
||||
static struct security_hook_list landlock_hooks[] __ro_after_init = {
|
||||
LSM_HOOK_INIT(inode_free_security, hook_inode_free_security),
|
||||
LSM_HOOK_INIT(inode_free_security_rcu, hook_inode_free_security_rcu),
|
||||
|
||||
LSM_HOOK_INIT(sb_delete, hook_sb_delete),
|
||||
LSM_HOOK_INIT(sb_mount, hook_sb_mount),
|
||||
|
||||
@@ -76,7 +76,7 @@ static struct security_hook_list lockdown_hooks[] __ro_after_init = {
|
||||
LSM_HOOK_INIT(locked_down, lockdown_is_locked_down),
|
||||
};
|
||||
|
||||
const struct lsm_id lockdown_lsmid = {
|
||||
static const struct lsm_id lockdown_lsmid = {
|
||||
.name = "lockdown",
|
||||
.id = LSM_ID_LOCKDOWN,
|
||||
};
|
||||
|
||||
+444
-171
@@ -28,30 +28,29 @@
|
||||
#include <linux/xattr.h>
|
||||
#include <linux/msg.h>
|
||||
#include <linux/overflow.h>
|
||||
#include <linux/perf_event.h>
|
||||
#include <linux/fs.h>
|
||||
#include <net/flow.h>
|
||||
#include <net/sock.h>
|
||||
|
||||
/* How many LSMs were built into the kernel? */
|
||||
#define LSM_COUNT (__end_lsm_info - __start_lsm_info)
|
||||
#define SECURITY_HOOK_ACTIVE_KEY(HOOK, IDX) security_hook_active_##HOOK##_##IDX
|
||||
|
||||
/*
|
||||
* How many LSMs are built into the kernel as determined at
|
||||
* build time. Used to determine fixed array sizes.
|
||||
* The capability module is accounted for by CONFIG_SECURITY
|
||||
* Identifier for the LSM static calls.
|
||||
* HOOK is an LSM hook as defined in linux/lsm_hookdefs.h
|
||||
* IDX is the index of the static call. 0 <= NUM < MAX_LSM_COUNT
|
||||
*/
|
||||
#define LSM_CONFIG_COUNT ( \
|
||||
(IS_ENABLED(CONFIG_SECURITY) ? 1 : 0) + \
|
||||
(IS_ENABLED(CONFIG_SECURITY_SELINUX) ? 1 : 0) + \
|
||||
(IS_ENABLED(CONFIG_SECURITY_SMACK) ? 1 : 0) + \
|
||||
(IS_ENABLED(CONFIG_SECURITY_TOMOYO) ? 1 : 0) + \
|
||||
(IS_ENABLED(CONFIG_SECURITY_APPARMOR) ? 1 : 0) + \
|
||||
(IS_ENABLED(CONFIG_SECURITY_YAMA) ? 1 : 0) + \
|
||||
(IS_ENABLED(CONFIG_SECURITY_LOADPIN) ? 1 : 0) + \
|
||||
(IS_ENABLED(CONFIG_SECURITY_SAFESETID) ? 1 : 0) + \
|
||||
(IS_ENABLED(CONFIG_SECURITY_LOCKDOWN_LSM) ? 1 : 0) + \
|
||||
(IS_ENABLED(CONFIG_BPF_LSM) ? 1 : 0) + \
|
||||
(IS_ENABLED(CONFIG_SECURITY_LANDLOCK) ? 1 : 0) + \
|
||||
(IS_ENABLED(CONFIG_IMA) ? 1 : 0) + \
|
||||
(IS_ENABLED(CONFIG_EVM) ? 1 : 0))
|
||||
#define LSM_STATIC_CALL(HOOK, IDX) lsm_static_call_##HOOK##_##IDX
|
||||
|
||||
/*
|
||||
* Call the macro M for each LSM hook MAX_LSM_COUNT times.
|
||||
*/
|
||||
#define LSM_LOOP_UNROLL(M, ...) \
|
||||
do { \
|
||||
UNROLL(MAX_LSM_COUNT, M, __VA_ARGS__) \
|
||||
} while (0)
|
||||
|
||||
#define LSM_DEFINE_UNROLL(M, ...) UNROLL(MAX_LSM_COUNT, M, __VA_ARGS__)
|
||||
|
||||
/*
|
||||
* These are descriptions of the reasons that can be passed to the
|
||||
@@ -92,7 +91,6 @@ const char *const lockdown_reasons[LOCKDOWN_CONFIDENTIALITY_MAX + 1] = {
|
||||
[LOCKDOWN_CONFIDENTIALITY_MAX] = "confidentiality",
|
||||
};
|
||||
|
||||
struct security_hook_heads security_hook_heads __ro_after_init;
|
||||
static BLOCKING_NOTIFIER_HEAD(blocking_lsm_notifier_chain);
|
||||
|
||||
static struct kmem_cache *lsm_file_cache;
|
||||
@@ -108,9 +106,58 @@ static __initdata const char *chosen_major_lsm;
|
||||
static __initconst const char *const builtin_lsm_order = CONFIG_LSM;
|
||||
|
||||
/* Ordered list of LSMs to initialize. */
|
||||
static __initdata struct lsm_info **ordered_lsms;
|
||||
static __initdata struct lsm_info *ordered_lsms[MAX_LSM_COUNT + 1];
|
||||
static __initdata struct lsm_info *exclusive;
|
||||
|
||||
#ifdef CONFIG_HAVE_STATIC_CALL
|
||||
#define LSM_HOOK_TRAMP(NAME, NUM) \
|
||||
&STATIC_CALL_TRAMP(LSM_STATIC_CALL(NAME, NUM))
|
||||
#else
|
||||
#define LSM_HOOK_TRAMP(NAME, NUM) NULL
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Define static calls and static keys for each LSM hook.
|
||||
*/
|
||||
#define DEFINE_LSM_STATIC_CALL(NUM, NAME, RET, ...) \
|
||||
DEFINE_STATIC_CALL_NULL(LSM_STATIC_CALL(NAME, NUM), \
|
||||
*((RET(*)(__VA_ARGS__))NULL)); \
|
||||
DEFINE_STATIC_KEY_FALSE(SECURITY_HOOK_ACTIVE_KEY(NAME, NUM));
|
||||
|
||||
#define LSM_HOOK(RET, DEFAULT, NAME, ...) \
|
||||
LSM_DEFINE_UNROLL(DEFINE_LSM_STATIC_CALL, NAME, RET, __VA_ARGS__)
|
||||
#include <linux/lsm_hook_defs.h>
|
||||
#undef LSM_HOOK
|
||||
#undef DEFINE_LSM_STATIC_CALL
|
||||
|
||||
/*
|
||||
* Initialise a table of static calls for each LSM hook.
|
||||
* DEFINE_STATIC_CALL_NULL invocation above generates a key (STATIC_CALL_KEY)
|
||||
* and a trampoline (STATIC_CALL_TRAMP) which are used to call
|
||||
* __static_call_update when updating the static call.
|
||||
*
|
||||
* The static calls table is used by early LSMs, some architectures can fault on
|
||||
* unaligned accesses and the fault handling code may not be ready by then.
|
||||
* Thus, the static calls table should be aligned to avoid any unhandled faults
|
||||
* in early init.
|
||||
*/
|
||||
struct lsm_static_calls_table
|
||||
static_calls_table __ro_after_init __aligned(sizeof(u64)) = {
|
||||
#define INIT_LSM_STATIC_CALL(NUM, NAME) \
|
||||
(struct lsm_static_call) { \
|
||||
.key = &STATIC_CALL_KEY(LSM_STATIC_CALL(NAME, NUM)), \
|
||||
.trampoline = LSM_HOOK_TRAMP(NAME, NUM), \
|
||||
.active = &SECURITY_HOOK_ACTIVE_KEY(NAME, NUM), \
|
||||
},
|
||||
#define LSM_HOOK(RET, DEFAULT, NAME, ...) \
|
||||
.NAME = { \
|
||||
LSM_DEFINE_UNROLL(INIT_LSM_STATIC_CALL, NAME) \
|
||||
},
|
||||
#include <linux/lsm_hook_defs.h>
|
||||
#undef LSM_HOOK
|
||||
#undef INIT_LSM_STATIC_CALL
|
||||
};
|
||||
|
||||
static __initdata bool debug;
|
||||
#define init_debug(...) \
|
||||
do { \
|
||||
@@ -171,7 +218,7 @@ static void __init append_ordered_lsm(struct lsm_info *lsm, const char *from)
|
||||
if (exists_ordered_lsm(lsm))
|
||||
return;
|
||||
|
||||
if (WARN(last_lsm == LSM_COUNT, "%s: out of LSM slots!?\n", from))
|
||||
if (WARN(last_lsm == MAX_LSM_COUNT, "%s: out of LSM static calls!?\n", from))
|
||||
return;
|
||||
|
||||
/* Enable this LSM, if it is not already set. */
|
||||
@@ -218,6 +265,7 @@ static void __init lsm_set_blob_sizes(struct lsm_blob_sizes *needed)
|
||||
|
||||
lsm_set_blob_size(&needed->lbs_cred, &blob_sizes.lbs_cred);
|
||||
lsm_set_blob_size(&needed->lbs_file, &blob_sizes.lbs_file);
|
||||
lsm_set_blob_size(&needed->lbs_ib, &blob_sizes.lbs_ib);
|
||||
/*
|
||||
* The inode blob gets an rcu_head in addition to
|
||||
* what the modules might need.
|
||||
@@ -226,11 +274,16 @@ static void __init lsm_set_blob_sizes(struct lsm_blob_sizes *needed)
|
||||
blob_sizes.lbs_inode = sizeof(struct rcu_head);
|
||||
lsm_set_blob_size(&needed->lbs_inode, &blob_sizes.lbs_inode);
|
||||
lsm_set_blob_size(&needed->lbs_ipc, &blob_sizes.lbs_ipc);
|
||||
lsm_set_blob_size(&needed->lbs_key, &blob_sizes.lbs_key);
|
||||
lsm_set_blob_size(&needed->lbs_msg_msg, &blob_sizes.lbs_msg_msg);
|
||||
lsm_set_blob_size(&needed->lbs_perf_event, &blob_sizes.lbs_perf_event);
|
||||
lsm_set_blob_size(&needed->lbs_sock, &blob_sizes.lbs_sock);
|
||||
lsm_set_blob_size(&needed->lbs_superblock, &blob_sizes.lbs_superblock);
|
||||
lsm_set_blob_size(&needed->lbs_task, &blob_sizes.lbs_task);
|
||||
lsm_set_blob_size(&needed->lbs_tun_dev, &blob_sizes.lbs_tun_dev);
|
||||
lsm_set_blob_size(&needed->lbs_xattr_count,
|
||||
&blob_sizes.lbs_xattr_count);
|
||||
lsm_set_blob_size(&needed->lbs_bdev, &blob_sizes.lbs_bdev);
|
||||
}
|
||||
|
||||
/* Prepare LSM for initialization. */
|
||||
@@ -268,7 +321,7 @@ static void __init initialize_lsm(struct lsm_info *lsm)
|
||||
* Current index to use while initializing the lsm id list.
|
||||
*/
|
||||
u32 lsm_active_cnt __ro_after_init;
|
||||
const struct lsm_id *lsm_idlist[LSM_CONFIG_COUNT];
|
||||
const struct lsm_id *lsm_idlist[MAX_LSM_COUNT];
|
||||
|
||||
/* Populate ordered LSMs list from comma-separated LSM name list. */
|
||||
static void __init ordered_lsm_parse(const char *order, const char *origin)
|
||||
@@ -350,6 +403,25 @@ static void __init ordered_lsm_parse(const char *order, const char *origin)
|
||||
kfree(sep);
|
||||
}
|
||||
|
||||
static void __init lsm_static_call_init(struct security_hook_list *hl)
|
||||
{
|
||||
struct lsm_static_call *scall = hl->scalls;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MAX_LSM_COUNT; i++) {
|
||||
/* Update the first static call that is not used yet */
|
||||
if (!scall->hl) {
|
||||
__static_call_update(scall->key, scall->trampoline,
|
||||
hl->hook.lsm_func_addr);
|
||||
scall->hl = hl;
|
||||
static_branch_enable(scall->active);
|
||||
return;
|
||||
}
|
||||
scall++;
|
||||
}
|
||||
panic("%s - Ran out of static slots.\n", __func__);
|
||||
}
|
||||
|
||||
static void __init lsm_early_cred(struct cred *cred);
|
||||
static void __init lsm_early_task(struct task_struct *task);
|
||||
|
||||
@@ -378,9 +450,6 @@ static void __init ordered_lsm_init(void)
|
||||
{
|
||||
struct lsm_info **lsm;
|
||||
|
||||
ordered_lsms = kcalloc(LSM_COUNT + 1, sizeof(*ordered_lsms),
|
||||
GFP_KERNEL);
|
||||
|
||||
if (chosen_lsm_order) {
|
||||
if (chosen_major_lsm) {
|
||||
pr_warn("security=%s is ignored because it is superseded by lsm=%s\n",
|
||||
@@ -398,12 +467,20 @@ static void __init ordered_lsm_init(void)
|
||||
|
||||
init_debug("cred blob size = %d\n", blob_sizes.lbs_cred);
|
||||
init_debug("file blob size = %d\n", blob_sizes.lbs_file);
|
||||
init_debug("ib blob size = %d\n", blob_sizes.lbs_ib);
|
||||
init_debug("inode blob size = %d\n", blob_sizes.lbs_inode);
|
||||
init_debug("ipc blob size = %d\n", blob_sizes.lbs_ipc);
|
||||
#ifdef CONFIG_KEYS
|
||||
init_debug("key blob size = %d\n", blob_sizes.lbs_key);
|
||||
#endif /* CONFIG_KEYS */
|
||||
init_debug("msg_msg blob size = %d\n", blob_sizes.lbs_msg_msg);
|
||||
init_debug("sock blob size = %d\n", blob_sizes.lbs_sock);
|
||||
init_debug("superblock blob size = %d\n", blob_sizes.lbs_superblock);
|
||||
init_debug("perf event blob size = %d\n", blob_sizes.lbs_perf_event);
|
||||
init_debug("task blob size = %d\n", blob_sizes.lbs_task);
|
||||
init_debug("tun device blob size = %d\n", blob_sizes.lbs_tun_dev);
|
||||
init_debug("xattr slots = %d\n", blob_sizes.lbs_xattr_count);
|
||||
init_debug("bdev blob size = %d\n", blob_sizes.lbs_bdev);
|
||||
|
||||
/*
|
||||
* Create any kmem_caches needed for blobs
|
||||
@@ -421,19 +498,12 @@ static void __init ordered_lsm_init(void)
|
||||
lsm_early_task(current);
|
||||
for (lsm = ordered_lsms; *lsm; lsm++)
|
||||
initialize_lsm(*lsm);
|
||||
|
||||
kfree(ordered_lsms);
|
||||
}
|
||||
|
||||
int __init early_security_init(void)
|
||||
{
|
||||
struct lsm_info *lsm;
|
||||
|
||||
#define LSM_HOOK(RET, DEFAULT, NAME, ...) \
|
||||
INIT_HLIST_HEAD(&security_hook_heads.NAME);
|
||||
#include "linux/lsm_hook_defs.h"
|
||||
#undef LSM_HOOK
|
||||
|
||||
for (lsm = __start_early_lsm_info; lsm < __end_early_lsm_info; lsm++) {
|
||||
if (!lsm->enabled)
|
||||
lsm->enabled = &lsm_enabled_true;
|
||||
@@ -554,14 +624,14 @@ void __init security_add_hooks(struct security_hook_list *hooks, int count,
|
||||
* Look at the previous entry, if there is one, for duplication.
|
||||
*/
|
||||
if (lsm_active_cnt == 0 || lsm_idlist[lsm_active_cnt - 1] != lsmid) {
|
||||
if (lsm_active_cnt >= LSM_CONFIG_COUNT)
|
||||
if (lsm_active_cnt >= MAX_LSM_COUNT)
|
||||
panic("%s Too many LSMs registered.\n", __func__);
|
||||
lsm_idlist[lsm_active_cnt++] = lsmid;
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
hooks[i].lsmid = lsmid;
|
||||
hlist_add_tail_rcu(&hooks[i].list, hooks[i].head);
|
||||
lsm_static_call_init(&hooks[i]);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -595,6 +665,29 @@ int unregister_blocking_lsm_notifier(struct notifier_block *nb)
|
||||
}
|
||||
EXPORT_SYMBOL(unregister_blocking_lsm_notifier);
|
||||
|
||||
/**
|
||||
* lsm_blob_alloc - allocate a composite blob
|
||||
* @dest: the destination for the blob
|
||||
* @size: the size of the blob
|
||||
* @gfp: allocation type
|
||||
*
|
||||
* Allocate a blob for all the modules
|
||||
*
|
||||
* Returns 0, or -ENOMEM if memory can't be allocated.
|
||||
*/
|
||||
static int lsm_blob_alloc(void **dest, size_t size, gfp_t gfp)
|
||||
{
|
||||
if (size == 0) {
|
||||
*dest = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
*dest = kzalloc(size, gfp);
|
||||
if (*dest == NULL)
|
||||
return -ENOMEM;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* lsm_cred_alloc - allocate a composite cred blob
|
||||
* @cred: the cred that needs a blob
|
||||
@@ -606,15 +699,7 @@ EXPORT_SYMBOL(unregister_blocking_lsm_notifier);
|
||||
*/
|
||||
static int lsm_cred_alloc(struct cred *cred, gfp_t gfp)
|
||||
{
|
||||
if (blob_sizes.lbs_cred == 0) {
|
||||
cred->security = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
cred->security = kzalloc(blob_sizes.lbs_cred, gfp);
|
||||
if (cred->security == NULL)
|
||||
return -ENOMEM;
|
||||
return 0;
|
||||
return lsm_blob_alloc(&cred->security, blob_sizes.lbs_cred, gfp);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -660,7 +745,7 @@ static int lsm_file_alloc(struct file *file)
|
||||
*
|
||||
* Returns 0, or -ENOMEM if memory can't be allocated.
|
||||
*/
|
||||
int lsm_inode_alloc(struct inode *inode)
|
||||
static int lsm_inode_alloc(struct inode *inode)
|
||||
{
|
||||
if (!lsm_inode_cache) {
|
||||
inode->i_security = NULL;
|
||||
@@ -683,15 +768,7 @@ int lsm_inode_alloc(struct inode *inode)
|
||||
*/
|
||||
static int lsm_task_alloc(struct task_struct *task)
|
||||
{
|
||||
if (blob_sizes.lbs_task == 0) {
|
||||
task->security = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
task->security = kzalloc(blob_sizes.lbs_task, GFP_KERNEL);
|
||||
if (task->security == NULL)
|
||||
return -ENOMEM;
|
||||
return 0;
|
||||
return lsm_blob_alloc(&task->security, blob_sizes.lbs_task, GFP_KERNEL);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -704,17 +781,24 @@ static int lsm_task_alloc(struct task_struct *task)
|
||||
*/
|
||||
static int lsm_ipc_alloc(struct kern_ipc_perm *kip)
|
||||
{
|
||||
if (blob_sizes.lbs_ipc == 0) {
|
||||
kip->security = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
kip->security = kzalloc(blob_sizes.lbs_ipc, GFP_KERNEL);
|
||||
if (kip->security == NULL)
|
||||
return -ENOMEM;
|
||||
return 0;
|
||||
return lsm_blob_alloc(&kip->security, blob_sizes.lbs_ipc, GFP_KERNEL);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KEYS
|
||||
/**
|
||||
* lsm_key_alloc - allocate a composite key blob
|
||||
* @key: the key that needs a blob
|
||||
*
|
||||
* Allocate the key blob for all the modules
|
||||
*
|
||||
* Returns 0, or -ENOMEM if memory can't be allocated.
|
||||
*/
|
||||
static int lsm_key_alloc(struct key *key)
|
||||
{
|
||||
return lsm_blob_alloc(&key->security, blob_sizes.lbs_key, GFP_KERNEL);
|
||||
}
|
||||
#endif /* CONFIG_KEYS */
|
||||
|
||||
/**
|
||||
* lsm_msg_msg_alloc - allocate a composite msg_msg blob
|
||||
* @mp: the msg_msg that needs a blob
|
||||
@@ -725,14 +809,29 @@ static int lsm_ipc_alloc(struct kern_ipc_perm *kip)
|
||||
*/
|
||||
static int lsm_msg_msg_alloc(struct msg_msg *mp)
|
||||
{
|
||||
if (blob_sizes.lbs_msg_msg == 0) {
|
||||
mp->security = NULL;
|
||||
return lsm_blob_alloc(&mp->security, blob_sizes.lbs_msg_msg,
|
||||
GFP_KERNEL);
|
||||
}
|
||||
|
||||
/**
|
||||
* lsm_bdev_alloc - allocate a composite block_device blob
|
||||
* @bdev: the block_device that needs a blob
|
||||
*
|
||||
* Allocate the block_device blob for all the modules
|
||||
*
|
||||
* Returns 0, or -ENOMEM if memory can't be allocated.
|
||||
*/
|
||||
static int lsm_bdev_alloc(struct block_device *bdev)
|
||||
{
|
||||
if (blob_sizes.lbs_bdev == 0) {
|
||||
bdev->bd_security = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
mp->security = kzalloc(blob_sizes.lbs_msg_msg, GFP_KERNEL);
|
||||
if (mp->security == NULL)
|
||||
bdev->bd_security = kzalloc(blob_sizes.lbs_bdev, GFP_KERNEL);
|
||||
if (!bdev->bd_security)
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -760,15 +859,8 @@ static void __init lsm_early_task(struct task_struct *task)
|
||||
*/
|
||||
static int lsm_superblock_alloc(struct super_block *sb)
|
||||
{
|
||||
if (blob_sizes.lbs_superblock == 0) {
|
||||
sb->s_security = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
sb->s_security = kzalloc(blob_sizes.lbs_superblock, GFP_KERNEL);
|
||||
if (sb->s_security == NULL)
|
||||
return -ENOMEM;
|
||||
return 0;
|
||||
return lsm_blob_alloc(&sb->s_security, blob_sizes.lbs_superblock,
|
||||
GFP_KERNEL);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -853,29 +945,43 @@ out:
|
||||
* call_int_hook:
|
||||
* This is a hook that returns a value.
|
||||
*/
|
||||
#define __CALL_STATIC_VOID(NUM, HOOK, ...) \
|
||||
do { \
|
||||
if (static_branch_unlikely(&SECURITY_HOOK_ACTIVE_KEY(HOOK, NUM))) { \
|
||||
static_call(LSM_STATIC_CALL(HOOK, NUM))(__VA_ARGS__); \
|
||||
} \
|
||||
} while (0);
|
||||
|
||||
#define call_void_hook(FUNC, ...) \
|
||||
do { \
|
||||
struct security_hook_list *P; \
|
||||
\
|
||||
hlist_for_each_entry(P, &security_hook_heads.FUNC, list) \
|
||||
P->hook.FUNC(__VA_ARGS__); \
|
||||
#define call_void_hook(HOOK, ...) \
|
||||
do { \
|
||||
LSM_LOOP_UNROLL(__CALL_STATIC_VOID, HOOK, __VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
#define call_int_hook(FUNC, ...) ({ \
|
||||
int RC = LSM_RET_DEFAULT(FUNC); \
|
||||
do { \
|
||||
struct security_hook_list *P; \
|
||||
\
|
||||
hlist_for_each_entry(P, &security_hook_heads.FUNC, list) { \
|
||||
RC = P->hook.FUNC(__VA_ARGS__); \
|
||||
if (RC != LSM_RET_DEFAULT(FUNC)) \
|
||||
break; \
|
||||
} \
|
||||
} while (0); \
|
||||
RC; \
|
||||
|
||||
#define __CALL_STATIC_INT(NUM, R, HOOK, LABEL, ...) \
|
||||
do { \
|
||||
if (static_branch_unlikely(&SECURITY_HOOK_ACTIVE_KEY(HOOK, NUM))) { \
|
||||
R = static_call(LSM_STATIC_CALL(HOOK, NUM))(__VA_ARGS__); \
|
||||
if (R != LSM_RET_DEFAULT(HOOK)) \
|
||||
goto LABEL; \
|
||||
} \
|
||||
} while (0);
|
||||
|
||||
#define call_int_hook(HOOK, ...) \
|
||||
({ \
|
||||
__label__ OUT; \
|
||||
int RC = LSM_RET_DEFAULT(HOOK); \
|
||||
\
|
||||
LSM_LOOP_UNROLL(__CALL_STATIC_INT, RC, HOOK, OUT, __VA_ARGS__); \
|
||||
OUT: \
|
||||
RC; \
|
||||
})
|
||||
|
||||
#define lsm_for_each_hook(scall, NAME) \
|
||||
for (scall = static_calls_table.NAME; \
|
||||
scall - static_calls_table.NAME < MAX_LSM_COUNT; scall++) \
|
||||
if (static_key_enabled(&scall->active->key))
|
||||
|
||||
/* Security operations */
|
||||
|
||||
/**
|
||||
@@ -1110,20 +1216,19 @@ int security_settime64(const struct timespec64 *ts, const struct timezone *tz)
|
||||
*/
|
||||
int security_vm_enough_memory_mm(struct mm_struct *mm, long pages)
|
||||
{
|
||||
struct security_hook_list *hp;
|
||||
struct lsm_static_call *scall;
|
||||
int cap_sys_admin = 1;
|
||||
int rc;
|
||||
|
||||
/*
|
||||
* The module will respond with a positive value if
|
||||
* it thinks the __vm_enough_memory() call should be
|
||||
* made with the cap_sys_admin set. If all of the modules
|
||||
* agree that it should be set it will. If any module
|
||||
* thinks it should not be set it won't.
|
||||
* The module will respond with 0 if it thinks the __vm_enough_memory()
|
||||
* call should be made with the cap_sys_admin set. If all of the modules
|
||||
* agree that it should be set it will. If any module thinks it should
|
||||
* not be set it won't.
|
||||
*/
|
||||
hlist_for_each_entry(hp, &security_hook_heads.vm_enough_memory, list) {
|
||||
rc = hp->hook.vm_enough_memory(mm, pages);
|
||||
if (rc <= 0) {
|
||||
lsm_for_each_hook(scall, vm_enough_memory) {
|
||||
rc = scall->hl->hook.vm_enough_memory(mm, pages);
|
||||
if (rc < 0) {
|
||||
cap_sys_admin = 0;
|
||||
break;
|
||||
}
|
||||
@@ -1269,13 +1374,12 @@ int security_fs_context_dup(struct fs_context *fc, struct fs_context *src_fc)
|
||||
int security_fs_context_parse_param(struct fs_context *fc,
|
||||
struct fs_parameter *param)
|
||||
{
|
||||
struct security_hook_list *hp;
|
||||
struct lsm_static_call *scall;
|
||||
int trc;
|
||||
int rc = -ENOPARAM;
|
||||
|
||||
hlist_for_each_entry(hp, &security_hook_heads.fs_context_parse_param,
|
||||
list) {
|
||||
trc = hp->hook.fs_context_parse_param(fc, param);
|
||||
lsm_for_each_hook(scall, fs_context_parse_param) {
|
||||
trc = scall->hl->hook.fs_context_parse_param(fc, param);
|
||||
if (trc == 0)
|
||||
rc = 0;
|
||||
else if (trc != -ENOPARAM)
|
||||
@@ -1505,12 +1609,11 @@ int security_sb_set_mnt_opts(struct super_block *sb,
|
||||
unsigned long kern_flags,
|
||||
unsigned long *set_kern_flags)
|
||||
{
|
||||
struct security_hook_list *hp;
|
||||
struct lsm_static_call *scall;
|
||||
int rc = mnt_opts ? -EOPNOTSUPP : LSM_RET_DEFAULT(sb_set_mnt_opts);
|
||||
|
||||
hlist_for_each_entry(hp, &security_hook_heads.sb_set_mnt_opts,
|
||||
list) {
|
||||
rc = hp->hook.sb_set_mnt_opts(sb, mnt_opts, kern_flags,
|
||||
lsm_for_each_hook(scall, sb_set_mnt_opts) {
|
||||
rc = scall->hl->hook.sb_set_mnt_opts(sb, mnt_opts, kern_flags,
|
||||
set_kern_flags);
|
||||
if (rc != LSM_RET_DEFAULT(sb_set_mnt_opts))
|
||||
break;
|
||||
@@ -1596,9 +1699,8 @@ int security_inode_alloc(struct inode *inode)
|
||||
|
||||
static void inode_free_by_rcu(struct rcu_head *head)
|
||||
{
|
||||
/*
|
||||
* The rcu head is at the start of the inode blob
|
||||
*/
|
||||
/* The rcu head is at the start of the inode blob */
|
||||
call_void_hook(inode_free_security_rcu, head);
|
||||
kmem_cache_free(lsm_inode_cache, head);
|
||||
}
|
||||
|
||||
@@ -1606,23 +1708,24 @@ static void inode_free_by_rcu(struct rcu_head *head)
|
||||
* security_inode_free() - Free an inode's LSM blob
|
||||
* @inode: the inode
|
||||
*
|
||||
* Deallocate the inode security structure and set @inode->i_security to NULL.
|
||||
* Release any LSM resources associated with @inode, although due to the
|
||||
* inode's RCU protections it is possible that the resources will not be
|
||||
* fully released until after the current RCU grace period has elapsed.
|
||||
*
|
||||
* It is important for LSMs to note that despite being present in a call to
|
||||
* security_inode_free(), @inode may still be referenced in a VFS path walk
|
||||
* and calls to security_inode_permission() may be made during, or after,
|
||||
* a call to security_inode_free(). For this reason the inode->i_security
|
||||
* field is released via a call_rcu() callback and any LSMs which need to
|
||||
* retain inode state for use in security_inode_permission() should only
|
||||
* release that state in the inode_free_security_rcu() LSM hook callback.
|
||||
*/
|
||||
void security_inode_free(struct inode *inode)
|
||||
{
|
||||
call_void_hook(inode_free_security, inode);
|
||||
/*
|
||||
* The inode may still be referenced in a path walk and
|
||||
* a call to security_inode_permission() can be made
|
||||
* after inode_free_security() is called. Ideally, the VFS
|
||||
* wouldn't do this, but fixing that is a much harder
|
||||
* job. For now, simply free the i_security via RCU, and
|
||||
* leave the current inode->i_security pointer intact.
|
||||
* The inode will be freed after the RCU grace period too.
|
||||
*/
|
||||
if (inode->i_security)
|
||||
call_rcu((struct rcu_head *)inode->i_security,
|
||||
inode_free_by_rcu);
|
||||
if (!inode->i_security)
|
||||
return;
|
||||
call_rcu((struct rcu_head *)inode->i_security, inode_free_by_rcu);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1705,7 +1808,7 @@ int security_inode_init_security(struct inode *inode, struct inode *dir,
|
||||
const struct qstr *qstr,
|
||||
const initxattrs initxattrs, void *fs_data)
|
||||
{
|
||||
struct security_hook_list *hp;
|
||||
struct lsm_static_call *scall;
|
||||
struct xattr *new_xattrs = NULL;
|
||||
int ret = -EOPNOTSUPP, xattr_count = 0;
|
||||
|
||||
@@ -1723,9 +1826,8 @@ int security_inode_init_security(struct inode *inode, struct inode *dir,
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
hlist_for_each_entry(hp, &security_hook_heads.inode_init_security,
|
||||
list) {
|
||||
ret = hp->hook.inode_init_security(inode, dir, qstr, new_xattrs,
|
||||
lsm_for_each_hook(scall, inode_init_security) {
|
||||
ret = scall->hl->hook.inode_init_security(inode, dir, qstr, new_xattrs,
|
||||
&xattr_count);
|
||||
if (ret && ret != -EOPNOTSUPP)
|
||||
goto out;
|
||||
@@ -2661,19 +2763,14 @@ EXPORT_SYMBOL(security_inode_copy_up);
|
||||
* lower layer to the union/overlay layer. The caller is responsible for
|
||||
* reading and writing the xattrs, this hook is merely a filter.
|
||||
*
|
||||
* Return: Returns 0 to accept the xattr, 1 to discard the xattr, -EOPNOTSUPP
|
||||
* if the security module does not know about attribute, or a negative
|
||||
* error code to abort the copy up.
|
||||
* Return: Returns 0 to accept the xattr, -ECANCELED to discard the xattr,
|
||||
* -EOPNOTSUPP if the security module does not know about attribute,
|
||||
* or a negative error code to abort the copy up.
|
||||
*/
|
||||
int security_inode_copy_up_xattr(struct dentry *src, const char *name)
|
||||
{
|
||||
int rc;
|
||||
|
||||
/*
|
||||
* The implementation can return 0 (accept the xattr), 1 (discard the
|
||||
* xattr), -EOPNOTSUPP if it does not know anything about the xattr or
|
||||
* any other error code in case of an error.
|
||||
*/
|
||||
rc = call_int_hook(inode_copy_up_xattr, src, name);
|
||||
if (rc != LSM_RET_DEFAULT(inode_copy_up_xattr))
|
||||
return rc;
|
||||
@@ -2682,6 +2779,26 @@ int security_inode_copy_up_xattr(struct dentry *src, const char *name)
|
||||
}
|
||||
EXPORT_SYMBOL(security_inode_copy_up_xattr);
|
||||
|
||||
/**
|
||||
* security_inode_setintegrity() - Set the inode's integrity data
|
||||
* @inode: inode
|
||||
* @type: type of integrity, e.g. hash digest, signature, etc
|
||||
* @value: the integrity value
|
||||
* @size: size of the integrity value
|
||||
*
|
||||
* Register a verified integrity measurement of a inode with LSMs.
|
||||
* LSMs should free the previously saved data if @value is NULL.
|
||||
*
|
||||
* Return: Returns 0 on success, negative values on failure.
|
||||
*/
|
||||
int security_inode_setintegrity(const struct inode *inode,
|
||||
enum lsm_integrity_type type, const void *value,
|
||||
size_t size)
|
||||
{
|
||||
return call_int_hook(inode_setintegrity, inode, type, value, size);
|
||||
}
|
||||
EXPORT_SYMBOL(security_inode_setintegrity);
|
||||
|
||||
/**
|
||||
* security_kernfs_init_security() - Init LSM context for a kernfs node
|
||||
* @kn_dir: parent kernfs node
|
||||
@@ -2931,6 +3048,8 @@ int security_file_fcntl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
* Save owner security information (typically from current->security) in
|
||||
* file->f_security for later use by the send_sigiotask hook.
|
||||
*
|
||||
* This hook is called with file->f_owner.lock held.
|
||||
*
|
||||
* Return: Returns 0 on success.
|
||||
*/
|
||||
void security_file_set_fowner(struct file *file)
|
||||
@@ -3557,10 +3676,10 @@ int security_task_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
{
|
||||
int thisrc;
|
||||
int rc = LSM_RET_DEFAULT(task_prctl);
|
||||
struct security_hook_list *hp;
|
||||
struct lsm_static_call *scall;
|
||||
|
||||
hlist_for_each_entry(hp, &security_hook_heads.task_prctl, list) {
|
||||
thisrc = hp->hook.task_prctl(option, arg2, arg3, arg4, arg5);
|
||||
lsm_for_each_hook(scall, task_prctl) {
|
||||
thisrc = scall->hl->hook.task_prctl(option, arg2, arg3, arg4, arg5);
|
||||
if (thisrc != LSM_RET_DEFAULT(task_prctl)) {
|
||||
rc = thisrc;
|
||||
if (thisrc != 0)
|
||||
@@ -3966,7 +4085,7 @@ EXPORT_SYMBOL(security_d_instantiate);
|
||||
int security_getselfattr(unsigned int attr, struct lsm_ctx __user *uctx,
|
||||
u32 __user *size, u32 flags)
|
||||
{
|
||||
struct security_hook_list *hp;
|
||||
struct lsm_static_call *scall;
|
||||
struct lsm_ctx lctx = { .id = LSM_ID_UNDEF, };
|
||||
u8 __user *base = (u8 __user *)uctx;
|
||||
u32 entrysize;
|
||||
@@ -4004,13 +4123,13 @@ int security_getselfattr(unsigned int attr, struct lsm_ctx __user *uctx,
|
||||
* In the usual case gather all the data from the LSMs.
|
||||
* In the single case only get the data from the LSM specified.
|
||||
*/
|
||||
hlist_for_each_entry(hp, &security_hook_heads.getselfattr, list) {
|
||||
if (single && lctx.id != hp->lsmid->id)
|
||||
lsm_for_each_hook(scall, getselfattr) {
|
||||
if (single && lctx.id != scall->hl->lsmid->id)
|
||||
continue;
|
||||
entrysize = left;
|
||||
if (base)
|
||||
uctx = (struct lsm_ctx __user *)(base + total);
|
||||
rc = hp->hook.getselfattr(attr, uctx, &entrysize, flags);
|
||||
rc = scall->hl->hook.getselfattr(attr, uctx, &entrysize, flags);
|
||||
if (rc == -EOPNOTSUPP) {
|
||||
rc = 0;
|
||||
continue;
|
||||
@@ -4059,7 +4178,7 @@ int security_getselfattr(unsigned int attr, struct lsm_ctx __user *uctx,
|
||||
int security_setselfattr(unsigned int attr, struct lsm_ctx __user *uctx,
|
||||
u32 size, u32 flags)
|
||||
{
|
||||
struct security_hook_list *hp;
|
||||
struct lsm_static_call *scall;
|
||||
struct lsm_ctx *lctx;
|
||||
int rc = LSM_RET_DEFAULT(setselfattr);
|
||||
u64 required_len;
|
||||
@@ -4082,9 +4201,9 @@ int security_setselfattr(unsigned int attr, struct lsm_ctx __user *uctx,
|
||||
goto free_out;
|
||||
}
|
||||
|
||||
hlist_for_each_entry(hp, &security_hook_heads.setselfattr, list)
|
||||
if ((hp->lsmid->id) == lctx->id) {
|
||||
rc = hp->hook.setselfattr(attr, lctx, size, flags);
|
||||
lsm_for_each_hook(scall, setselfattr)
|
||||
if ((scall->hl->lsmid->id) == lctx->id) {
|
||||
rc = scall->hl->hook.setselfattr(attr, lctx, size, flags);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -4107,12 +4226,12 @@ free_out:
|
||||
int security_getprocattr(struct task_struct *p, int lsmid, const char *name,
|
||||
char **value)
|
||||
{
|
||||
struct security_hook_list *hp;
|
||||
struct lsm_static_call *scall;
|
||||
|
||||
hlist_for_each_entry(hp, &security_hook_heads.getprocattr, list) {
|
||||
if (lsmid != 0 && lsmid != hp->lsmid->id)
|
||||
lsm_for_each_hook(scall, getprocattr) {
|
||||
if (lsmid != 0 && lsmid != scall->hl->lsmid->id)
|
||||
continue;
|
||||
return hp->hook.getprocattr(p, name, value);
|
||||
return scall->hl->hook.getprocattr(p, name, value);
|
||||
}
|
||||
return LSM_RET_DEFAULT(getprocattr);
|
||||
}
|
||||
@@ -4131,12 +4250,12 @@ int security_getprocattr(struct task_struct *p, int lsmid, const char *name,
|
||||
*/
|
||||
int security_setprocattr(int lsmid, const char *name, void *value, size_t size)
|
||||
{
|
||||
struct security_hook_list *hp;
|
||||
struct lsm_static_call *scall;
|
||||
|
||||
hlist_for_each_entry(hp, &security_hook_heads.setprocattr, list) {
|
||||
if (lsmid != 0 && lsmid != hp->lsmid->id)
|
||||
lsm_for_each_hook(scall, setprocattr) {
|
||||
if (lsmid != 0 && lsmid != scall->hl->lsmid->id)
|
||||
continue;
|
||||
return hp->hook.setprocattr(name, value, size);
|
||||
return scall->hl->hook.setprocattr(name, value, size);
|
||||
}
|
||||
return LSM_RET_DEFAULT(setprocattr);
|
||||
}
|
||||
@@ -4673,6 +4792,20 @@ int security_socket_getpeersec_dgram(struct socket *sock,
|
||||
}
|
||||
EXPORT_SYMBOL(security_socket_getpeersec_dgram);
|
||||
|
||||
/**
|
||||
* lsm_sock_alloc - allocate a composite sock blob
|
||||
* @sock: the sock that needs a blob
|
||||
* @gfp: allocation mode
|
||||
*
|
||||
* Allocate the sock blob for all the modules
|
||||
*
|
||||
* Returns 0, or -ENOMEM if memory can't be allocated.
|
||||
*/
|
||||
static int lsm_sock_alloc(struct sock *sock, gfp_t gfp)
|
||||
{
|
||||
return lsm_blob_alloc(&sock->sk_security, blob_sizes.lbs_sock, gfp);
|
||||
}
|
||||
|
||||
/**
|
||||
* security_sk_alloc() - Allocate and initialize a sock's LSM blob
|
||||
* @sk: sock
|
||||
@@ -4686,7 +4819,14 @@ EXPORT_SYMBOL(security_socket_getpeersec_dgram);
|
||||
*/
|
||||
int security_sk_alloc(struct sock *sk, int family, gfp_t priority)
|
||||
{
|
||||
return call_int_hook(sk_alloc_security, sk, family, priority);
|
||||
int rc = lsm_sock_alloc(sk, priority);
|
||||
|
||||
if (unlikely(rc))
|
||||
return rc;
|
||||
rc = call_int_hook(sk_alloc_security, sk, family, priority);
|
||||
if (unlikely(rc))
|
||||
security_sk_free(sk);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -4698,6 +4838,8 @@ int security_sk_alloc(struct sock *sk, int family, gfp_t priority)
|
||||
void security_sk_free(struct sock *sk)
|
||||
{
|
||||
call_void_hook(sk_free_security, sk);
|
||||
kfree(sk->sk_security);
|
||||
sk->sk_security = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -4845,7 +4987,18 @@ EXPORT_SYMBOL(security_secmark_refcount_dec);
|
||||
*/
|
||||
int security_tun_dev_alloc_security(void **security)
|
||||
{
|
||||
return call_int_hook(tun_dev_alloc_security, security);
|
||||
int rc;
|
||||
|
||||
rc = lsm_blob_alloc(security, blob_sizes.lbs_tun_dev, GFP_KERNEL);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = call_int_hook(tun_dev_alloc_security, *security);
|
||||
if (rc) {
|
||||
kfree(*security);
|
||||
*security = NULL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
EXPORT_SYMBOL(security_tun_dev_alloc_security);
|
||||
|
||||
@@ -4857,7 +5010,7 @@ EXPORT_SYMBOL(security_tun_dev_alloc_security);
|
||||
*/
|
||||
void security_tun_dev_free_security(void *security)
|
||||
{
|
||||
call_void_hook(tun_dev_free_security, security);
|
||||
kfree(security);
|
||||
}
|
||||
EXPORT_SYMBOL(security_tun_dev_free_security);
|
||||
|
||||
@@ -5053,7 +5206,18 @@ EXPORT_SYMBOL(security_ib_endport_manage_subnet);
|
||||
*/
|
||||
int security_ib_alloc_security(void **sec)
|
||||
{
|
||||
return call_int_hook(ib_alloc_security, sec);
|
||||
int rc;
|
||||
|
||||
rc = lsm_blob_alloc(sec, blob_sizes.lbs_ib, GFP_KERNEL);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = call_int_hook(ib_alloc_security, *sec);
|
||||
if (rc) {
|
||||
kfree(*sec);
|
||||
*sec = NULL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
EXPORT_SYMBOL(security_ib_alloc_security);
|
||||
|
||||
@@ -5065,7 +5229,7 @@ EXPORT_SYMBOL(security_ib_alloc_security);
|
||||
*/
|
||||
void security_ib_free_security(void *sec)
|
||||
{
|
||||
call_void_hook(ib_free_security, sec);
|
||||
kfree(sec);
|
||||
}
|
||||
EXPORT_SYMBOL(security_ib_free_security);
|
||||
#endif /* CONFIG_SECURITY_INFINIBAND */
|
||||
@@ -5223,7 +5387,7 @@ int security_xfrm_state_pol_flow_match(struct xfrm_state *x,
|
||||
struct xfrm_policy *xp,
|
||||
const struct flowi_common *flic)
|
||||
{
|
||||
struct security_hook_list *hp;
|
||||
struct lsm_static_call *scall;
|
||||
int rc = LSM_RET_DEFAULT(xfrm_state_pol_flow_match);
|
||||
|
||||
/*
|
||||
@@ -5235,9 +5399,8 @@ int security_xfrm_state_pol_flow_match(struct xfrm_state *x,
|
||||
* For speed optimization, we explicitly break the loop rather than
|
||||
* using the macro
|
||||
*/
|
||||
hlist_for_each_entry(hp, &security_hook_heads.xfrm_state_pol_flow_match,
|
||||
list) {
|
||||
rc = hp->hook.xfrm_state_pol_flow_match(x, xp, flic);
|
||||
lsm_for_each_hook(scall, xfrm_state_pol_flow_match) {
|
||||
rc = scall->hl->hook.xfrm_state_pol_flow_match(x, xp, flic);
|
||||
break;
|
||||
}
|
||||
return rc;
|
||||
@@ -5282,7 +5445,14 @@ EXPORT_SYMBOL(security_skb_classify_flow);
|
||||
int security_key_alloc(struct key *key, const struct cred *cred,
|
||||
unsigned long flags)
|
||||
{
|
||||
return call_int_hook(key_alloc, key, cred, flags);
|
||||
int rc = lsm_key_alloc(key);
|
||||
|
||||
if (unlikely(rc))
|
||||
return rc;
|
||||
rc = call_int_hook(key_alloc, key, cred, flags);
|
||||
if (unlikely(rc))
|
||||
security_key_free(key);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -5293,7 +5463,8 @@ int security_key_alloc(struct key *key, const struct cred *cred,
|
||||
*/
|
||||
void security_key_free(struct key *key)
|
||||
{
|
||||
call_void_hook(key_free, key);
|
||||
kfree(key->security);
|
||||
key->security = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -5596,6 +5767,85 @@ int security_locked_down(enum lockdown_reason what)
|
||||
}
|
||||
EXPORT_SYMBOL(security_locked_down);
|
||||
|
||||
/**
|
||||
* security_bdev_alloc() - Allocate a block device LSM blob
|
||||
* @bdev: block device
|
||||
*
|
||||
* Allocate and attach a security structure to @bdev->bd_security. The
|
||||
* security field is initialized to NULL when the bdev structure is
|
||||
* allocated.
|
||||
*
|
||||
* Return: Return 0 if operation was successful.
|
||||
*/
|
||||
int security_bdev_alloc(struct block_device *bdev)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
rc = lsm_bdev_alloc(bdev);
|
||||
if (unlikely(rc))
|
||||
return rc;
|
||||
|
||||
rc = call_int_hook(bdev_alloc_security, bdev);
|
||||
if (unlikely(rc))
|
||||
security_bdev_free(bdev);
|
||||
|
||||
return rc;
|
||||
}
|
||||
EXPORT_SYMBOL(security_bdev_alloc);
|
||||
|
||||
/**
|
||||
* security_bdev_free() - Free a block device's LSM blob
|
||||
* @bdev: block device
|
||||
*
|
||||
* Deallocate the bdev security structure and set @bdev->bd_security to NULL.
|
||||
*/
|
||||
void security_bdev_free(struct block_device *bdev)
|
||||
{
|
||||
if (!bdev->bd_security)
|
||||
return;
|
||||
|
||||
call_void_hook(bdev_free_security, bdev);
|
||||
|
||||
kfree(bdev->bd_security);
|
||||
bdev->bd_security = NULL;
|
||||
}
|
||||
EXPORT_SYMBOL(security_bdev_free);
|
||||
|
||||
/**
|
||||
* security_bdev_setintegrity() - Set the device's integrity data
|
||||
* @bdev: block device
|
||||
* @type: type of integrity, e.g. hash digest, signature, etc
|
||||
* @value: the integrity value
|
||||
* @size: size of the integrity value
|
||||
*
|
||||
* Register a verified integrity measurement of a bdev with LSMs.
|
||||
* LSMs should free the previously saved data if @value is NULL.
|
||||
* Please note that the new hook should be invoked every time the security
|
||||
* information is updated to keep these data current. For example, in dm-verity,
|
||||
* if the mapping table is reloaded and configured to use a different dm-verity
|
||||
* target with a new roothash and signing information, the previously stored
|
||||
* data in the LSM blob will become obsolete. It is crucial to re-invoke the
|
||||
* hook to refresh these data and ensure they are up to date. This necessity
|
||||
* arises from the design of device-mapper, where a device-mapper device is
|
||||
* first created, and then targets are subsequently loaded into it. These
|
||||
* targets can be modified multiple times during the device's lifetime.
|
||||
* Therefore, while the LSM blob is allocated during the creation of the block
|
||||
* device, its actual contents are not initialized at this stage and can change
|
||||
* substantially over time. This includes alterations from data that the LSMs
|
||||
* 'trusts' to those they do not, making it essential to handle these changes
|
||||
* correctly. Failure to address this dynamic aspect could potentially allow
|
||||
* for bypassing LSM checks.
|
||||
*
|
||||
* Return: Returns 0 on success, negative values on failure.
|
||||
*/
|
||||
int security_bdev_setintegrity(struct block_device *bdev,
|
||||
enum lsm_integrity_type type, const void *value,
|
||||
size_t size)
|
||||
{
|
||||
return call_int_hook(bdev_setintegrity, bdev, type, value, size);
|
||||
}
|
||||
EXPORT_SYMBOL(security_bdev_setintegrity);
|
||||
|
||||
#ifdef CONFIG_PERF_EVENTS
|
||||
/**
|
||||
* security_perf_event_open() - Check if a perf event open is allowed
|
||||
@@ -5621,7 +5871,19 @@ int security_perf_event_open(struct perf_event_attr *attr, int type)
|
||||
*/
|
||||
int security_perf_event_alloc(struct perf_event *event)
|
||||
{
|
||||
return call_int_hook(perf_event_alloc, event);
|
||||
int rc;
|
||||
|
||||
rc = lsm_blob_alloc(&event->security, blob_sizes.lbs_perf_event,
|
||||
GFP_KERNEL);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = call_int_hook(perf_event_alloc, event);
|
||||
if (rc) {
|
||||
kfree(event->security);
|
||||
event->security = NULL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -5632,7 +5894,8 @@ int security_perf_event_alloc(struct perf_event *event)
|
||||
*/
|
||||
void security_perf_event_free(struct perf_event *event)
|
||||
{
|
||||
call_void_hook(perf_event_free, event);
|
||||
kfree(event->security);
|
||||
event->security = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -5703,3 +5966,13 @@ int security_uring_cmd(struct io_uring_cmd *ioucmd)
|
||||
return call_int_hook(uring_cmd, ioucmd);
|
||||
}
|
||||
#endif /* CONFIG_IO_URING */
|
||||
|
||||
/**
|
||||
* security_initramfs_populated() - Notify LSMs that initramfs has been loaded
|
||||
*
|
||||
* Tells the LSMs the initramfs has been unpacked into the rootfs.
|
||||
*/
|
||||
void security_initramfs_populated(void)
|
||||
{
|
||||
call_void_hook(initramfs_populated);
|
||||
}
|
||||
|
||||
+60
-116
@@ -2207,23 +2207,16 @@ static int selinux_syslog(int type)
|
||||
}
|
||||
|
||||
/*
|
||||
* Check that a process has enough memory to allocate a new virtual
|
||||
* mapping. 0 means there is enough memory for the allocation to
|
||||
* succeed and -ENOMEM implies there is not.
|
||||
* Check permission for allocating a new virtual mapping. Returns
|
||||
* 0 if permission is granted, negative error code if not.
|
||||
*
|
||||
* Do not audit the selinux permission check, as this is applied to all
|
||||
* processes that allocate mappings.
|
||||
*/
|
||||
static int selinux_vm_enough_memory(struct mm_struct *mm, long pages)
|
||||
{
|
||||
int rc, cap_sys_admin = 0;
|
||||
|
||||
rc = cred_has_capability(current_cred(), CAP_SYS_ADMIN,
|
||||
CAP_OPT_NOAUDIT, true);
|
||||
if (rc == 0)
|
||||
cap_sys_admin = 1;
|
||||
|
||||
return cap_sys_admin;
|
||||
return cred_has_capability(current_cred(), CAP_SYS_ADMIN,
|
||||
CAP_OPT_NOAUDIT, true);
|
||||
}
|
||||
|
||||
/* binprm security operations */
|
||||
@@ -3543,8 +3536,8 @@ static int selinux_inode_copy_up_xattr(struct dentry *dentry, const char *name)
|
||||
* xattrs up. Instead, filter out SELinux-related xattrs following
|
||||
* policy load.
|
||||
*/
|
||||
if (selinux_initialized() && strcmp(name, XATTR_NAME_SELINUX) == 0)
|
||||
return 1; /* Discard */
|
||||
if (selinux_initialized() && !strcmp(name, XATTR_NAME_SELINUX))
|
||||
return -ECANCELED; /* Discard */
|
||||
/*
|
||||
* Any other attribute apart from SELINUX is not claimed, supported
|
||||
* by selinux.
|
||||
@@ -4599,7 +4592,7 @@ static int socket_sockcreate_sid(const struct task_security_struct *tsec,
|
||||
|
||||
static int sock_has_perm(struct sock *sk, u32 perms)
|
||||
{
|
||||
struct sk_security_struct *sksec = sk->sk_security;
|
||||
struct sk_security_struct *sksec = selinux_sock(sk);
|
||||
struct common_audit_data ad;
|
||||
struct lsm_network_audit net;
|
||||
|
||||
@@ -4667,7 +4660,7 @@ static int selinux_socket_post_create(struct socket *sock, int family,
|
||||
isec->initialized = LABEL_INITIALIZED;
|
||||
|
||||
if (sock->sk) {
|
||||
sksec = sock->sk->sk_security;
|
||||
sksec = selinux_sock(sock->sk);
|
||||
sksec->sclass = sclass;
|
||||
sksec->sid = sid;
|
||||
/* Allows detection of the first association on this socket */
|
||||
@@ -4683,8 +4676,8 @@ static int selinux_socket_post_create(struct socket *sock, int family,
|
||||
static int selinux_socket_socketpair(struct socket *socka,
|
||||
struct socket *sockb)
|
||||
{
|
||||
struct sk_security_struct *sksec_a = socka->sk->sk_security;
|
||||
struct sk_security_struct *sksec_b = sockb->sk->sk_security;
|
||||
struct sk_security_struct *sksec_a = selinux_sock(socka->sk);
|
||||
struct sk_security_struct *sksec_b = selinux_sock(sockb->sk);
|
||||
|
||||
sksec_a->peer_sid = sksec_b->sid;
|
||||
sksec_b->peer_sid = sksec_a->sid;
|
||||
@@ -4699,7 +4692,7 @@ static int selinux_socket_socketpair(struct socket *socka,
|
||||
static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, int addrlen)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
struct sk_security_struct *sksec = sk->sk_security;
|
||||
struct sk_security_struct *sksec = selinux_sock(sk);
|
||||
u16 family;
|
||||
int err;
|
||||
|
||||
@@ -4839,7 +4832,7 @@ static int selinux_socket_connect_helper(struct socket *sock,
|
||||
struct sockaddr *address, int addrlen)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
struct sk_security_struct *sksec = sk->sk_security;
|
||||
struct sk_security_struct *sksec = selinux_sock(sk);
|
||||
int err;
|
||||
|
||||
err = sock_has_perm(sk, SOCKET__CONNECT);
|
||||
@@ -5017,9 +5010,9 @@ static int selinux_socket_unix_stream_connect(struct sock *sock,
|
||||
struct sock *other,
|
||||
struct sock *newsk)
|
||||
{
|
||||
struct sk_security_struct *sksec_sock = sock->sk_security;
|
||||
struct sk_security_struct *sksec_other = other->sk_security;
|
||||
struct sk_security_struct *sksec_new = newsk->sk_security;
|
||||
struct sk_security_struct *sksec_sock = selinux_sock(sock);
|
||||
struct sk_security_struct *sksec_other = selinux_sock(other);
|
||||
struct sk_security_struct *sksec_new = selinux_sock(newsk);
|
||||
struct common_audit_data ad;
|
||||
struct lsm_network_audit net;
|
||||
int err;
|
||||
@@ -5048,8 +5041,8 @@ static int selinux_socket_unix_stream_connect(struct sock *sock,
|
||||
static int selinux_socket_unix_may_send(struct socket *sock,
|
||||
struct socket *other)
|
||||
{
|
||||
struct sk_security_struct *ssec = sock->sk->sk_security;
|
||||
struct sk_security_struct *osec = other->sk->sk_security;
|
||||
struct sk_security_struct *ssec = selinux_sock(sock->sk);
|
||||
struct sk_security_struct *osec = selinux_sock(other->sk);
|
||||
struct common_audit_data ad;
|
||||
struct lsm_network_audit net;
|
||||
|
||||
@@ -5086,7 +5079,7 @@ static int selinux_sock_rcv_skb_compat(struct sock *sk, struct sk_buff *skb,
|
||||
u16 family)
|
||||
{
|
||||
int err = 0;
|
||||
struct sk_security_struct *sksec = sk->sk_security;
|
||||
struct sk_security_struct *sksec = selinux_sock(sk);
|
||||
u32 sk_sid = sksec->sid;
|
||||
struct common_audit_data ad;
|
||||
struct lsm_network_audit net;
|
||||
@@ -5115,7 +5108,7 @@ static int selinux_sock_rcv_skb_compat(struct sock *sk, struct sk_buff *skb,
|
||||
static int selinux_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb)
|
||||
{
|
||||
int err, peerlbl_active, secmark_active;
|
||||
struct sk_security_struct *sksec = sk->sk_security;
|
||||
struct sk_security_struct *sksec = selinux_sock(sk);
|
||||
u16 family = sk->sk_family;
|
||||
u32 sk_sid = sksec->sid;
|
||||
struct common_audit_data ad;
|
||||
@@ -5183,7 +5176,7 @@ static int selinux_socket_getpeersec_stream(struct socket *sock,
|
||||
int err = 0;
|
||||
char *scontext = NULL;
|
||||
u32 scontext_len;
|
||||
struct sk_security_struct *sksec = sock->sk->sk_security;
|
||||
struct sk_security_struct *sksec = selinux_sock(sock->sk);
|
||||
u32 peer_sid = SECSID_NULL;
|
||||
|
||||
if (sksec->sclass == SECCLASS_UNIX_STREAM_SOCKET ||
|
||||
@@ -5243,34 +5236,27 @@ static int selinux_socket_getpeersec_dgram(struct socket *sock,
|
||||
|
||||
static int selinux_sk_alloc_security(struct sock *sk, int family, gfp_t priority)
|
||||
{
|
||||
struct sk_security_struct *sksec;
|
||||
|
||||
sksec = kzalloc(sizeof(*sksec), priority);
|
||||
if (!sksec)
|
||||
return -ENOMEM;
|
||||
struct sk_security_struct *sksec = selinux_sock(sk);
|
||||
|
||||
sksec->peer_sid = SECINITSID_UNLABELED;
|
||||
sksec->sid = SECINITSID_UNLABELED;
|
||||
sksec->sclass = SECCLASS_SOCKET;
|
||||
selinux_netlbl_sk_security_reset(sksec);
|
||||
sk->sk_security = sksec;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void selinux_sk_free_security(struct sock *sk)
|
||||
{
|
||||
struct sk_security_struct *sksec = sk->sk_security;
|
||||
struct sk_security_struct *sksec = selinux_sock(sk);
|
||||
|
||||
sk->sk_security = NULL;
|
||||
selinux_netlbl_sk_security_free(sksec);
|
||||
kfree(sksec);
|
||||
}
|
||||
|
||||
static void selinux_sk_clone_security(const struct sock *sk, struct sock *newsk)
|
||||
{
|
||||
struct sk_security_struct *sksec = sk->sk_security;
|
||||
struct sk_security_struct *newsksec = newsk->sk_security;
|
||||
struct sk_security_struct *sksec = selinux_sock(sk);
|
||||
struct sk_security_struct *newsksec = selinux_sock(newsk);
|
||||
|
||||
newsksec->sid = sksec->sid;
|
||||
newsksec->peer_sid = sksec->peer_sid;
|
||||
@@ -5284,7 +5270,7 @@ static void selinux_sk_getsecid(const struct sock *sk, u32 *secid)
|
||||
if (!sk)
|
||||
*secid = SECINITSID_ANY_SOCKET;
|
||||
else {
|
||||
const struct sk_security_struct *sksec = sk->sk_security;
|
||||
const struct sk_security_struct *sksec = selinux_sock(sk);
|
||||
|
||||
*secid = sksec->sid;
|
||||
}
|
||||
@@ -5294,7 +5280,7 @@ static void selinux_sock_graft(struct sock *sk, struct socket *parent)
|
||||
{
|
||||
struct inode_security_struct *isec =
|
||||
inode_security_novalidate(SOCK_INODE(parent));
|
||||
struct sk_security_struct *sksec = sk->sk_security;
|
||||
struct sk_security_struct *sksec = selinux_sock(sk);
|
||||
|
||||
if (sk->sk_family == PF_INET || sk->sk_family == PF_INET6 ||
|
||||
sk->sk_family == PF_UNIX)
|
||||
@@ -5311,7 +5297,7 @@ static int selinux_sctp_process_new_assoc(struct sctp_association *asoc,
|
||||
{
|
||||
struct sock *sk = asoc->base.sk;
|
||||
u16 family = sk->sk_family;
|
||||
struct sk_security_struct *sksec = sk->sk_security;
|
||||
struct sk_security_struct *sksec = selinux_sock(sk);
|
||||
struct common_audit_data ad;
|
||||
struct lsm_network_audit net;
|
||||
int err;
|
||||
@@ -5366,7 +5352,7 @@ static int selinux_sctp_process_new_assoc(struct sctp_association *asoc,
|
||||
static int selinux_sctp_assoc_request(struct sctp_association *asoc,
|
||||
struct sk_buff *skb)
|
||||
{
|
||||
struct sk_security_struct *sksec = asoc->base.sk->sk_security;
|
||||
struct sk_security_struct *sksec = selinux_sock(asoc->base.sk);
|
||||
u32 conn_sid;
|
||||
int err;
|
||||
|
||||
@@ -5399,7 +5385,7 @@ static int selinux_sctp_assoc_request(struct sctp_association *asoc,
|
||||
static int selinux_sctp_assoc_established(struct sctp_association *asoc,
|
||||
struct sk_buff *skb)
|
||||
{
|
||||
struct sk_security_struct *sksec = asoc->base.sk->sk_security;
|
||||
struct sk_security_struct *sksec = selinux_sock(asoc->base.sk);
|
||||
|
||||
if (!selinux_policycap_extsockclass())
|
||||
return 0;
|
||||
@@ -5498,8 +5484,8 @@ static int selinux_sctp_bind_connect(struct sock *sk, int optname,
|
||||
static void selinux_sctp_sk_clone(struct sctp_association *asoc, struct sock *sk,
|
||||
struct sock *newsk)
|
||||
{
|
||||
struct sk_security_struct *sksec = sk->sk_security;
|
||||
struct sk_security_struct *newsksec = newsk->sk_security;
|
||||
struct sk_security_struct *sksec = selinux_sock(sk);
|
||||
struct sk_security_struct *newsksec = selinux_sock(newsk);
|
||||
|
||||
/* If policy does not support SECCLASS_SCTP_SOCKET then call
|
||||
* the non-sctp clone version.
|
||||
@@ -5515,8 +5501,8 @@ static void selinux_sctp_sk_clone(struct sctp_association *asoc, struct sock *sk
|
||||
|
||||
static int selinux_mptcp_add_subflow(struct sock *sk, struct sock *ssk)
|
||||
{
|
||||
struct sk_security_struct *ssksec = ssk->sk_security;
|
||||
struct sk_security_struct *sksec = sk->sk_security;
|
||||
struct sk_security_struct *ssksec = selinux_sock(ssk);
|
||||
struct sk_security_struct *sksec = selinux_sock(sk);
|
||||
|
||||
ssksec->sclass = sksec->sclass;
|
||||
ssksec->sid = sksec->sid;
|
||||
@@ -5531,7 +5517,7 @@ static int selinux_mptcp_add_subflow(struct sock *sk, struct sock *ssk)
|
||||
static int selinux_inet_conn_request(const struct sock *sk, struct sk_buff *skb,
|
||||
struct request_sock *req)
|
||||
{
|
||||
struct sk_security_struct *sksec = sk->sk_security;
|
||||
struct sk_security_struct *sksec = selinux_sock(sk);
|
||||
int err;
|
||||
u16 family = req->rsk_ops->family;
|
||||
u32 connsid;
|
||||
@@ -5552,7 +5538,7 @@ static int selinux_inet_conn_request(const struct sock *sk, struct sk_buff *skb,
|
||||
static void selinux_inet_csk_clone(struct sock *newsk,
|
||||
const struct request_sock *req)
|
||||
{
|
||||
struct sk_security_struct *newsksec = newsk->sk_security;
|
||||
struct sk_security_struct *newsksec = selinux_sock(newsk);
|
||||
|
||||
newsksec->sid = req->secid;
|
||||
newsksec->peer_sid = req->peer_secid;
|
||||
@@ -5569,7 +5555,7 @@ static void selinux_inet_csk_clone(struct sock *newsk,
|
||||
static void selinux_inet_conn_established(struct sock *sk, struct sk_buff *skb)
|
||||
{
|
||||
u16 family = sk->sk_family;
|
||||
struct sk_security_struct *sksec = sk->sk_security;
|
||||
struct sk_security_struct *sksec = selinux_sock(sk);
|
||||
|
||||
/* handle mapped IPv4 packets arriving via IPv6 sockets */
|
||||
if (family == PF_INET6 && skb->protocol == htons(ETH_P_IP))
|
||||
@@ -5600,24 +5586,14 @@ static void selinux_req_classify_flow(const struct request_sock *req,
|
||||
flic->flowic_secid = req->secid;
|
||||
}
|
||||
|
||||
static int selinux_tun_dev_alloc_security(void **security)
|
||||
static int selinux_tun_dev_alloc_security(void *security)
|
||||
{
|
||||
struct tun_security_struct *tunsec;
|
||||
struct tun_security_struct *tunsec = selinux_tun_dev(security);
|
||||
|
||||
tunsec = kzalloc(sizeof(*tunsec), GFP_KERNEL);
|
||||
if (!tunsec)
|
||||
return -ENOMEM;
|
||||
tunsec->sid = current_sid();
|
||||
|
||||
*security = tunsec;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void selinux_tun_dev_free_security(void *security)
|
||||
{
|
||||
kfree(security);
|
||||
}
|
||||
|
||||
static int selinux_tun_dev_create(void)
|
||||
{
|
||||
u32 sid = current_sid();
|
||||
@@ -5635,7 +5611,7 @@ static int selinux_tun_dev_create(void)
|
||||
|
||||
static int selinux_tun_dev_attach_queue(void *security)
|
||||
{
|
||||
struct tun_security_struct *tunsec = security;
|
||||
struct tun_security_struct *tunsec = selinux_tun_dev(security);
|
||||
|
||||
return avc_has_perm(current_sid(), tunsec->sid, SECCLASS_TUN_SOCKET,
|
||||
TUN_SOCKET__ATTACH_QUEUE, NULL);
|
||||
@@ -5643,8 +5619,8 @@ static int selinux_tun_dev_attach_queue(void *security)
|
||||
|
||||
static int selinux_tun_dev_attach(struct sock *sk, void *security)
|
||||
{
|
||||
struct tun_security_struct *tunsec = security;
|
||||
struct sk_security_struct *sksec = sk->sk_security;
|
||||
struct tun_security_struct *tunsec = selinux_tun_dev(security);
|
||||
struct sk_security_struct *sksec = selinux_sock(sk);
|
||||
|
||||
/* we don't currently perform any NetLabel based labeling here and it
|
||||
* isn't clear that we would want to do so anyway; while we could apply
|
||||
@@ -5661,7 +5637,7 @@ static int selinux_tun_dev_attach(struct sock *sk, void *security)
|
||||
|
||||
static int selinux_tun_dev_open(void *security)
|
||||
{
|
||||
struct tun_security_struct *tunsec = security;
|
||||
struct tun_security_struct *tunsec = selinux_tun_dev(security);
|
||||
u32 sid = current_sid();
|
||||
int err;
|
||||
|
||||
@@ -5767,7 +5743,7 @@ static unsigned int selinux_ip_output(void *priv, struct sk_buff *skb,
|
||||
return NF_ACCEPT;
|
||||
|
||||
/* standard practice, label using the parent socket */
|
||||
sksec = sk->sk_security;
|
||||
sksec = selinux_sock(sk);
|
||||
sid = sksec->sid;
|
||||
} else
|
||||
sid = SECINITSID_KERNEL;
|
||||
@@ -5790,7 +5766,7 @@ static unsigned int selinux_ip_postroute_compat(struct sk_buff *skb,
|
||||
sk = skb_to_full_sk(skb);
|
||||
if (sk == NULL)
|
||||
return NF_ACCEPT;
|
||||
sksec = sk->sk_security;
|
||||
sksec = selinux_sock(sk);
|
||||
|
||||
ad_net_init_from_iif(&ad, &net, state->out->ifindex, state->pf);
|
||||
if (selinux_parse_skb(skb, &ad, NULL, 0, &proto))
|
||||
@@ -5879,7 +5855,7 @@ static unsigned int selinux_ip_postroute(void *priv,
|
||||
u32 skb_sid;
|
||||
struct sk_security_struct *sksec;
|
||||
|
||||
sksec = sk->sk_security;
|
||||
sksec = selinux_sock(sk);
|
||||
if (selinux_skb_peerlbl_sid(skb, family, &skb_sid))
|
||||
return NF_DROP;
|
||||
/* At this point, if the returned skb peerlbl is SECSID_NULL
|
||||
@@ -5908,7 +5884,7 @@ static unsigned int selinux_ip_postroute(void *priv,
|
||||
} else {
|
||||
/* Locally generated packet, fetch the security label from the
|
||||
* associated socket. */
|
||||
struct sk_security_struct *sksec = sk->sk_security;
|
||||
struct sk_security_struct *sksec = selinux_sock(sk);
|
||||
peer_sid = sksec->sid;
|
||||
secmark_perm = PACKET__SEND;
|
||||
}
|
||||
@@ -5951,7 +5927,7 @@ static int selinux_netlink_send(struct sock *sk, struct sk_buff *skb)
|
||||
unsigned int data_len = skb->len;
|
||||
unsigned char *data = skb->data;
|
||||
struct nlmsghdr *nlh;
|
||||
struct sk_security_struct *sksec = sk->sk_security;
|
||||
struct sk_security_struct *sksec = selinux_sock(sk);
|
||||
u16 sclass = sksec->sclass;
|
||||
u32 perm;
|
||||
|
||||
@@ -6685,11 +6661,7 @@ static int selinux_key_alloc(struct key *k, const struct cred *cred,
|
||||
unsigned long flags)
|
||||
{
|
||||
const struct task_security_struct *tsec;
|
||||
struct key_security_struct *ksec;
|
||||
|
||||
ksec = kzalloc(sizeof(struct key_security_struct), GFP_KERNEL);
|
||||
if (!ksec)
|
||||
return -ENOMEM;
|
||||
struct key_security_struct *ksec = selinux_key(k);
|
||||
|
||||
tsec = selinux_cred(cred);
|
||||
if (tsec->keycreate_sid)
|
||||
@@ -6697,18 +6669,9 @@ static int selinux_key_alloc(struct key *k, const struct cred *cred,
|
||||
else
|
||||
ksec->sid = tsec->sid;
|
||||
|
||||
k->security = ksec;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void selinux_key_free(struct key *k)
|
||||
{
|
||||
struct key_security_struct *ksec = k->security;
|
||||
|
||||
k->security = NULL;
|
||||
kfree(ksec);
|
||||
}
|
||||
|
||||
static int selinux_key_permission(key_ref_t key_ref,
|
||||
const struct cred *cred,
|
||||
enum key_need_perm need_perm)
|
||||
@@ -6749,14 +6712,14 @@ static int selinux_key_permission(key_ref_t key_ref,
|
||||
|
||||
sid = cred_sid(cred);
|
||||
key = key_ref_to_ptr(key_ref);
|
||||
ksec = key->security;
|
||||
ksec = selinux_key(key);
|
||||
|
||||
return avc_has_perm(sid, ksec->sid, SECCLASS_KEY, perm, NULL);
|
||||
}
|
||||
|
||||
static int selinux_key_getsecurity(struct key *key, char **_buffer)
|
||||
{
|
||||
struct key_security_struct *ksec = key->security;
|
||||
struct key_security_struct *ksec = selinux_key(key);
|
||||
char *context = NULL;
|
||||
unsigned len;
|
||||
int rc;
|
||||
@@ -6826,23 +6789,13 @@ static int selinux_ib_endport_manage_subnet(void *ib_sec, const char *dev_name,
|
||||
INFINIBAND_ENDPORT__MANAGE_SUBNET, &ad);
|
||||
}
|
||||
|
||||
static int selinux_ib_alloc_security(void **ib_sec)
|
||||
static int selinux_ib_alloc_security(void *ib_sec)
|
||||
{
|
||||
struct ib_security_struct *sec;
|
||||
struct ib_security_struct *sec = selinux_ib(ib_sec);
|
||||
|
||||
sec = kzalloc(sizeof(*sec), GFP_KERNEL);
|
||||
if (!sec)
|
||||
return -ENOMEM;
|
||||
sec->sid = current_sid();
|
||||
|
||||
*ib_sec = sec;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void selinux_ib_free_security(void *ib_sec)
|
||||
{
|
||||
kfree(ib_sec);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_BPF_SYSCALL
|
||||
@@ -7008,9 +6961,16 @@ struct lsm_blob_sizes selinux_blob_sizes __ro_after_init = {
|
||||
.lbs_file = sizeof(struct file_security_struct),
|
||||
.lbs_inode = sizeof(struct inode_security_struct),
|
||||
.lbs_ipc = sizeof(struct ipc_security_struct),
|
||||
.lbs_key = sizeof(struct key_security_struct),
|
||||
.lbs_msg_msg = sizeof(struct msg_security_struct),
|
||||
#ifdef CONFIG_PERF_EVENTS
|
||||
.lbs_perf_event = sizeof(struct perf_event_security_struct),
|
||||
#endif
|
||||
.lbs_sock = sizeof(struct sk_security_struct),
|
||||
.lbs_superblock = sizeof(struct superblock_security_struct),
|
||||
.lbs_xattr_count = SELINUX_INODE_INIT_XATTRS,
|
||||
.lbs_tun_dev = sizeof(struct tun_security_struct),
|
||||
.lbs_ib = sizeof(struct ib_security_struct),
|
||||
};
|
||||
|
||||
#ifdef CONFIG_PERF_EVENTS
|
||||
@@ -7037,24 +6997,12 @@ static int selinux_perf_event_alloc(struct perf_event *event)
|
||||
{
|
||||
struct perf_event_security_struct *perfsec;
|
||||
|
||||
perfsec = kzalloc(sizeof(*perfsec), GFP_KERNEL);
|
||||
if (!perfsec)
|
||||
return -ENOMEM;
|
||||
|
||||
perfsec = selinux_perf_event(event->security);
|
||||
perfsec->sid = current_sid();
|
||||
event->security = perfsec;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void selinux_perf_event_free(struct perf_event *event)
|
||||
{
|
||||
struct perf_event_security_struct *perfsec = event->security;
|
||||
|
||||
event->security = NULL;
|
||||
kfree(perfsec);
|
||||
}
|
||||
|
||||
static int selinux_perf_event_read(struct perf_event *event)
|
||||
{
|
||||
struct perf_event_security_struct *perfsec = event->security;
|
||||
@@ -7322,7 +7270,6 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = {
|
||||
LSM_HOOK_INIT(secmark_refcount_inc, selinux_secmark_refcount_inc),
|
||||
LSM_HOOK_INIT(secmark_refcount_dec, selinux_secmark_refcount_dec),
|
||||
LSM_HOOK_INIT(req_classify_flow, selinux_req_classify_flow),
|
||||
LSM_HOOK_INIT(tun_dev_free_security, selinux_tun_dev_free_security),
|
||||
LSM_HOOK_INIT(tun_dev_create, selinux_tun_dev_create),
|
||||
LSM_HOOK_INIT(tun_dev_attach_queue, selinux_tun_dev_attach_queue),
|
||||
LSM_HOOK_INIT(tun_dev_attach, selinux_tun_dev_attach),
|
||||
@@ -7331,7 +7278,6 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = {
|
||||
LSM_HOOK_INIT(ib_pkey_access, selinux_ib_pkey_access),
|
||||
LSM_HOOK_INIT(ib_endport_manage_subnet,
|
||||
selinux_ib_endport_manage_subnet),
|
||||
LSM_HOOK_INIT(ib_free_security, selinux_ib_free_security),
|
||||
#endif
|
||||
#ifdef CONFIG_SECURITY_NETWORK_XFRM
|
||||
LSM_HOOK_INIT(xfrm_policy_free_security, selinux_xfrm_policy_free),
|
||||
@@ -7345,7 +7291,6 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = {
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_KEYS
|
||||
LSM_HOOK_INIT(key_free, selinux_key_free),
|
||||
LSM_HOOK_INIT(key_permission, selinux_key_permission),
|
||||
LSM_HOOK_INIT(key_getsecurity, selinux_key_getsecurity),
|
||||
#ifdef CONFIG_KEY_NOTIFICATIONS
|
||||
@@ -7370,7 +7315,6 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = {
|
||||
|
||||
#ifdef CONFIG_PERF_EVENTS
|
||||
LSM_HOOK_INIT(perf_event_open, selinux_perf_event_open),
|
||||
LSM_HOOK_INIT(perf_event_free, selinux_perf_event_free),
|
||||
LSM_HOOK_INIT(perf_event_read, selinux_perf_event_read),
|
||||
LSM_HOOK_INIT(perf_event_write, selinux_perf_event_write),
|
||||
#endif
|
||||
|
||||
@@ -195,4 +195,32 @@ selinux_superblock(const struct super_block *superblock)
|
||||
return superblock->s_security + selinux_blob_sizes.lbs_superblock;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KEYS
|
||||
static inline struct key_security_struct *selinux_key(const struct key *key)
|
||||
{
|
||||
return key->security + selinux_blob_sizes.lbs_key;
|
||||
}
|
||||
#endif /* CONFIG_KEYS */
|
||||
|
||||
static inline struct sk_security_struct *selinux_sock(const struct sock *sock)
|
||||
{
|
||||
return sock->sk_security + selinux_blob_sizes.lbs_sock;
|
||||
}
|
||||
|
||||
static inline struct tun_security_struct *selinux_tun_dev(void *security)
|
||||
{
|
||||
return security + selinux_blob_sizes.lbs_tun_dev;
|
||||
}
|
||||
|
||||
static inline struct ib_security_struct *selinux_ib(void *ib_sec)
|
||||
{
|
||||
return ib_sec + selinux_blob_sizes.lbs_ib;
|
||||
}
|
||||
|
||||
static inline struct perf_event_security_struct *
|
||||
selinux_perf_event(void *perf_event)
|
||||
{
|
||||
return perf_event + selinux_blob_sizes.lbs_perf_event;
|
||||
}
|
||||
|
||||
#endif /* _SELINUX_OBJSEC_H_ */
|
||||
|
||||
+12
-11
@@ -17,6 +17,7 @@
|
||||
#include <linux/gfp.h>
|
||||
#include <linux/ip.h>
|
||||
#include <linux/ipv6.h>
|
||||
#include <linux/lsm_hooks.h>
|
||||
#include <net/sock.h>
|
||||
#include <net/netlabel.h>
|
||||
#include <net/ip.h>
|
||||
@@ -68,7 +69,7 @@ static int selinux_netlbl_sidlookup_cached(struct sk_buff *skb,
|
||||
static struct netlbl_lsm_secattr *selinux_netlbl_sock_genattr(struct sock *sk)
|
||||
{
|
||||
int rc;
|
||||
struct sk_security_struct *sksec = sk->sk_security;
|
||||
struct sk_security_struct *sksec = selinux_sock(sk);
|
||||
struct netlbl_lsm_secattr *secattr;
|
||||
|
||||
if (sksec->nlbl_secattr != NULL)
|
||||
@@ -101,7 +102,7 @@ static struct netlbl_lsm_secattr *selinux_netlbl_sock_getattr(
|
||||
const struct sock *sk,
|
||||
u32 sid)
|
||||
{
|
||||
struct sk_security_struct *sksec = sk->sk_security;
|
||||
struct sk_security_struct *sksec = selinux_sock(sk);
|
||||
struct netlbl_lsm_secattr *secattr = sksec->nlbl_secattr;
|
||||
|
||||
if (secattr == NULL)
|
||||
@@ -241,7 +242,7 @@ int selinux_netlbl_skbuff_setsid(struct sk_buff *skb,
|
||||
* being labeled by it's parent socket, if it is just exit */
|
||||
sk = skb_to_full_sk(skb);
|
||||
if (sk != NULL) {
|
||||
struct sk_security_struct *sksec = sk->sk_security;
|
||||
struct sk_security_struct *sksec = selinux_sock(sk);
|
||||
|
||||
if (sksec->nlbl_state != NLBL_REQSKB)
|
||||
return 0;
|
||||
@@ -278,7 +279,7 @@ int selinux_netlbl_sctp_assoc_request(struct sctp_association *asoc,
|
||||
{
|
||||
int rc;
|
||||
struct netlbl_lsm_secattr secattr;
|
||||
struct sk_security_struct *sksec = asoc->base.sk->sk_security;
|
||||
struct sk_security_struct *sksec = selinux_sock(asoc->base.sk);
|
||||
struct sockaddr_in addr4;
|
||||
struct sockaddr_in6 addr6;
|
||||
|
||||
@@ -357,7 +358,7 @@ inet_conn_request_return:
|
||||
*/
|
||||
void selinux_netlbl_inet_csk_clone(struct sock *sk, u16 family)
|
||||
{
|
||||
struct sk_security_struct *sksec = sk->sk_security;
|
||||
struct sk_security_struct *sksec = selinux_sock(sk);
|
||||
|
||||
if (family == PF_INET || family == PF_INET6)
|
||||
sksec->nlbl_state = NLBL_LABELED;
|
||||
@@ -375,8 +376,8 @@ void selinux_netlbl_inet_csk_clone(struct sock *sk, u16 family)
|
||||
*/
|
||||
void selinux_netlbl_sctp_sk_clone(struct sock *sk, struct sock *newsk)
|
||||
{
|
||||
struct sk_security_struct *sksec = sk->sk_security;
|
||||
struct sk_security_struct *newsksec = newsk->sk_security;
|
||||
struct sk_security_struct *sksec = selinux_sock(sk);
|
||||
struct sk_security_struct *newsksec = selinux_sock(newsk);
|
||||
|
||||
newsksec->nlbl_state = sksec->nlbl_state;
|
||||
}
|
||||
@@ -394,7 +395,7 @@ void selinux_netlbl_sctp_sk_clone(struct sock *sk, struct sock *newsk)
|
||||
int selinux_netlbl_socket_post_create(struct sock *sk, u16 family)
|
||||
{
|
||||
int rc;
|
||||
struct sk_security_struct *sksec = sk->sk_security;
|
||||
struct sk_security_struct *sksec = selinux_sock(sk);
|
||||
struct netlbl_lsm_secattr *secattr;
|
||||
|
||||
if (family != PF_INET && family != PF_INET6)
|
||||
@@ -511,7 +512,7 @@ int selinux_netlbl_socket_setsockopt(struct socket *sock,
|
||||
{
|
||||
int rc = 0;
|
||||
struct sock *sk = sock->sk;
|
||||
struct sk_security_struct *sksec = sk->sk_security;
|
||||
struct sk_security_struct *sksec = selinux_sock(sk);
|
||||
struct netlbl_lsm_secattr secattr;
|
||||
|
||||
if (selinux_netlbl_option(level, optname) &&
|
||||
@@ -549,7 +550,7 @@ static int selinux_netlbl_socket_connect_helper(struct sock *sk,
|
||||
struct sockaddr *addr)
|
||||
{
|
||||
int rc;
|
||||
struct sk_security_struct *sksec = sk->sk_security;
|
||||
struct sk_security_struct *sksec = selinux_sock(sk);
|
||||
struct netlbl_lsm_secattr *secattr;
|
||||
|
||||
/* connected sockets are allowed to disconnect when the address family
|
||||
@@ -587,7 +588,7 @@ static int selinux_netlbl_socket_connect_helper(struct sock *sk,
|
||||
int selinux_netlbl_socket_connect_locked(struct sock *sk,
|
||||
struct sockaddr *addr)
|
||||
{
|
||||
struct sk_security_struct *sksec = sk->sk_security;
|
||||
struct sk_security_struct *sksec = selinux_sock(sk);
|
||||
|
||||
if (sksec->nlbl_state != NLBL_REQSKB &&
|
||||
sksec->nlbl_state != NLBL_CONNLABELED)
|
||||
|
||||
@@ -355,6 +355,18 @@ static inline struct superblock_smack *smack_superblock(
|
||||
return superblock->s_security + smack_blob_sizes.lbs_superblock;
|
||||
}
|
||||
|
||||
static inline struct socket_smack *smack_sock(const struct sock *sock)
|
||||
{
|
||||
return sock->sk_security + smack_blob_sizes.lbs_sock;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KEYS
|
||||
static inline struct smack_known **smack_key(const struct key *key)
|
||||
{
|
||||
return key->security + smack_blob_sizes.lbs_key;
|
||||
}
|
||||
#endif /* CONFIG_KEYS */
|
||||
|
||||
/*
|
||||
* Is the directory transmuting?
|
||||
*/
|
||||
|
||||
+49
-58
@@ -1606,7 +1606,7 @@ static int smack_inode_getsecurity(struct mnt_idmap *idmap,
|
||||
if (sock == NULL || sock->sk == NULL)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
ssp = sock->sk->sk_security;
|
||||
ssp = smack_sock(sock->sk);
|
||||
|
||||
if (strcmp(name, XATTR_SMACK_IPIN) == 0)
|
||||
isp = ssp->smk_in;
|
||||
@@ -1994,7 +1994,7 @@ static int smack_file_receive(struct file *file)
|
||||
|
||||
if (inode->i_sb->s_magic == SOCKFS_MAGIC) {
|
||||
sock = SOCKET_I(inode);
|
||||
ssp = sock->sk->sk_security;
|
||||
ssp = smack_sock(sock->sk);
|
||||
tsp = smack_cred(current_cred());
|
||||
/*
|
||||
* If the receiving process can't write to the
|
||||
@@ -2409,11 +2409,7 @@ static void smack_task_to_inode(struct task_struct *p, struct inode *inode)
|
||||
static int smack_sk_alloc_security(struct sock *sk, int family, gfp_t gfp_flags)
|
||||
{
|
||||
struct smack_known *skp = smk_of_current();
|
||||
struct socket_smack *ssp;
|
||||
|
||||
ssp = kzalloc(sizeof(struct socket_smack), gfp_flags);
|
||||
if (ssp == NULL)
|
||||
return -ENOMEM;
|
||||
struct socket_smack *ssp = smack_sock(sk);
|
||||
|
||||
/*
|
||||
* Sockets created by kernel threads receive web label.
|
||||
@@ -2427,11 +2423,10 @@ static int smack_sk_alloc_security(struct sock *sk, int family, gfp_t gfp_flags)
|
||||
}
|
||||
ssp->smk_packet = NULL;
|
||||
|
||||
sk->sk_security = ssp;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef SMACK_IPV6_PORT_LABELING
|
||||
/**
|
||||
* smack_sk_free_security - Free a socket blob
|
||||
* @sk: the socket
|
||||
@@ -2440,7 +2435,6 @@ static int smack_sk_alloc_security(struct sock *sk, int family, gfp_t gfp_flags)
|
||||
*/
|
||||
static void smack_sk_free_security(struct sock *sk)
|
||||
{
|
||||
#ifdef SMACK_IPV6_PORT_LABELING
|
||||
struct smk_port_label *spp;
|
||||
|
||||
if (sk->sk_family == PF_INET6) {
|
||||
@@ -2453,9 +2447,8 @@ static void smack_sk_free_security(struct sock *sk)
|
||||
}
|
||||
rcu_read_unlock();
|
||||
}
|
||||
#endif
|
||||
kfree(sk->sk_security);
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* smack_sk_clone_security - Copy security context
|
||||
@@ -2466,8 +2459,8 @@ static void smack_sk_free_security(struct sock *sk)
|
||||
*/
|
||||
static void smack_sk_clone_security(const struct sock *sk, struct sock *newsk)
|
||||
{
|
||||
struct socket_smack *ssp_old = sk->sk_security;
|
||||
struct socket_smack *ssp_new = newsk->sk_security;
|
||||
struct socket_smack *ssp_old = smack_sock(sk);
|
||||
struct socket_smack *ssp_new = smack_sock(newsk);
|
||||
|
||||
*ssp_new = *ssp_old;
|
||||
}
|
||||
@@ -2583,7 +2576,7 @@ static struct smack_known *smack_ipv6host_label(struct sockaddr_in6 *sip)
|
||||
*/
|
||||
static int smack_netlbl_add(struct sock *sk)
|
||||
{
|
||||
struct socket_smack *ssp = sk->sk_security;
|
||||
struct socket_smack *ssp = smack_sock(sk);
|
||||
struct smack_known *skp = ssp->smk_out;
|
||||
int rc;
|
||||
|
||||
@@ -2616,7 +2609,7 @@ static int smack_netlbl_add(struct sock *sk)
|
||||
*/
|
||||
static void smack_netlbl_delete(struct sock *sk)
|
||||
{
|
||||
struct socket_smack *ssp = sk->sk_security;
|
||||
struct socket_smack *ssp = smack_sock(sk);
|
||||
|
||||
/*
|
||||
* Take the label off the socket if one is set.
|
||||
@@ -2648,7 +2641,7 @@ static int smk_ipv4_check(struct sock *sk, struct sockaddr_in *sap)
|
||||
struct smack_known *skp;
|
||||
int rc = 0;
|
||||
struct smack_known *hkp;
|
||||
struct socket_smack *ssp = sk->sk_security;
|
||||
struct socket_smack *ssp = smack_sock(sk);
|
||||
struct smk_audit_info ad;
|
||||
|
||||
rcu_read_lock();
|
||||
@@ -2721,7 +2714,7 @@ static void smk_ipv6_port_label(struct socket *sock, struct sockaddr *address)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
struct sockaddr_in6 *addr6;
|
||||
struct socket_smack *ssp = sock->sk->sk_security;
|
||||
struct socket_smack *ssp = smack_sock(sock->sk);
|
||||
struct smk_port_label *spp;
|
||||
unsigned short port = 0;
|
||||
|
||||
@@ -2809,7 +2802,7 @@ static int smk_ipv6_port_check(struct sock *sk, struct sockaddr_in6 *address,
|
||||
int act)
|
||||
{
|
||||
struct smk_port_label *spp;
|
||||
struct socket_smack *ssp = sk->sk_security;
|
||||
struct socket_smack *ssp = smack_sock(sk);
|
||||
struct smack_known *skp = NULL;
|
||||
unsigned short port;
|
||||
struct smack_known *object;
|
||||
@@ -2912,7 +2905,7 @@ static int smack_inode_setsecurity(struct inode *inode, const char *name,
|
||||
if (sock == NULL || sock->sk == NULL)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
ssp = sock->sk->sk_security;
|
||||
ssp = smack_sock(sock->sk);
|
||||
|
||||
if (strcmp(name, XATTR_SMACK_IPIN) == 0)
|
||||
ssp->smk_in = skp;
|
||||
@@ -2960,7 +2953,7 @@ static int smack_socket_post_create(struct socket *sock, int family,
|
||||
* Sockets created by kernel threads receive web label.
|
||||
*/
|
||||
if (unlikely(current->flags & PF_KTHREAD)) {
|
||||
ssp = sock->sk->sk_security;
|
||||
ssp = smack_sock(sock->sk);
|
||||
ssp->smk_in = &smack_known_web;
|
||||
ssp->smk_out = &smack_known_web;
|
||||
}
|
||||
@@ -2985,8 +2978,8 @@ static int smack_socket_post_create(struct socket *sock, int family,
|
||||
static int smack_socket_socketpair(struct socket *socka,
|
||||
struct socket *sockb)
|
||||
{
|
||||
struct socket_smack *asp = socka->sk->sk_security;
|
||||
struct socket_smack *bsp = sockb->sk->sk_security;
|
||||
struct socket_smack *asp = smack_sock(socka->sk);
|
||||
struct socket_smack *bsp = smack_sock(sockb->sk);
|
||||
|
||||
asp->smk_packet = bsp->smk_out;
|
||||
bsp->smk_packet = asp->smk_out;
|
||||
@@ -3049,7 +3042,7 @@ static int smack_socket_connect(struct socket *sock, struct sockaddr *sap,
|
||||
if (__is_defined(SMACK_IPV6_SECMARK_LABELING))
|
||||
rsp = smack_ipv6host_label(sip);
|
||||
if (rsp != NULL) {
|
||||
struct socket_smack *ssp = sock->sk->sk_security;
|
||||
struct socket_smack *ssp = smack_sock(sock->sk);
|
||||
|
||||
rc = smk_ipv6_check(ssp->smk_out, rsp, sip,
|
||||
SMK_CONNECTING);
|
||||
@@ -3844,9 +3837,9 @@ static int smack_unix_stream_connect(struct sock *sock,
|
||||
{
|
||||
struct smack_known *skp;
|
||||
struct smack_known *okp;
|
||||
struct socket_smack *ssp = sock->sk_security;
|
||||
struct socket_smack *osp = other->sk_security;
|
||||
struct socket_smack *nsp = newsk->sk_security;
|
||||
struct socket_smack *ssp = smack_sock(sock);
|
||||
struct socket_smack *osp = smack_sock(other);
|
||||
struct socket_smack *nsp = smack_sock(newsk);
|
||||
struct smk_audit_info ad;
|
||||
int rc = 0;
|
||||
#ifdef CONFIG_AUDIT
|
||||
@@ -3898,8 +3891,8 @@ static int smack_unix_stream_connect(struct sock *sock,
|
||||
*/
|
||||
static int smack_unix_may_send(struct socket *sock, struct socket *other)
|
||||
{
|
||||
struct socket_smack *ssp = sock->sk->sk_security;
|
||||
struct socket_smack *osp = other->sk->sk_security;
|
||||
struct socket_smack *ssp = smack_sock(sock->sk);
|
||||
struct socket_smack *osp = smack_sock(other->sk);
|
||||
struct smk_audit_info ad;
|
||||
int rc;
|
||||
|
||||
@@ -3936,7 +3929,7 @@ static int smack_socket_sendmsg(struct socket *sock, struct msghdr *msg,
|
||||
struct sockaddr_in6 *sap = (struct sockaddr_in6 *) msg->msg_name;
|
||||
#endif
|
||||
#ifdef SMACK_IPV6_SECMARK_LABELING
|
||||
struct socket_smack *ssp = sock->sk->sk_security;
|
||||
struct socket_smack *ssp = smack_sock(sock->sk);
|
||||
struct smack_known *rsp;
|
||||
#endif
|
||||
int rc = 0;
|
||||
@@ -4148,7 +4141,7 @@ static struct smack_known *smack_from_netlbl(const struct sock *sk, u16 family,
|
||||
netlbl_secattr_init(&secattr);
|
||||
|
||||
if (sk)
|
||||
ssp = sk->sk_security;
|
||||
ssp = smack_sock(sk);
|
||||
|
||||
if (netlbl_skbuff_getattr(skb, family, &secattr) == 0) {
|
||||
skp = smack_from_secattr(&secattr, ssp);
|
||||
@@ -4170,7 +4163,7 @@ static struct smack_known *smack_from_netlbl(const struct sock *sk, u16 family,
|
||||
*/
|
||||
static int smack_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb)
|
||||
{
|
||||
struct socket_smack *ssp = sk->sk_security;
|
||||
struct socket_smack *ssp = smack_sock(sk);
|
||||
struct smack_known *skp = NULL;
|
||||
int rc = 0;
|
||||
struct smk_audit_info ad;
|
||||
@@ -4274,7 +4267,7 @@ static int smack_socket_getpeersec_stream(struct socket *sock,
|
||||
u32 slen = 1;
|
||||
int rc = 0;
|
||||
|
||||
ssp = sock->sk->sk_security;
|
||||
ssp = smack_sock(sock->sk);
|
||||
if (ssp->smk_packet != NULL) {
|
||||
rcp = ssp->smk_packet->smk_known;
|
||||
slen = strlen(rcp) + 1;
|
||||
@@ -4324,7 +4317,7 @@ static int smack_socket_getpeersec_dgram(struct socket *sock,
|
||||
|
||||
switch (family) {
|
||||
case PF_UNIX:
|
||||
ssp = sock->sk->sk_security;
|
||||
ssp = smack_sock(sock->sk);
|
||||
s = ssp->smk_out->smk_secid;
|
||||
break;
|
||||
case PF_INET:
|
||||
@@ -4373,7 +4366,7 @@ static void smack_sock_graft(struct sock *sk, struct socket *parent)
|
||||
(sk->sk_family != PF_INET && sk->sk_family != PF_INET6))
|
||||
return;
|
||||
|
||||
ssp = sk->sk_security;
|
||||
ssp = smack_sock(sk);
|
||||
ssp->smk_in = skp;
|
||||
ssp->smk_out = skp;
|
||||
/* cssp->smk_packet is already set in smack_inet_csk_clone() */
|
||||
@@ -4393,7 +4386,7 @@ static int smack_inet_conn_request(const struct sock *sk, struct sk_buff *skb,
|
||||
{
|
||||
u16 family = sk->sk_family;
|
||||
struct smack_known *skp;
|
||||
struct socket_smack *ssp = sk->sk_security;
|
||||
struct socket_smack *ssp = smack_sock(sk);
|
||||
struct sockaddr_in addr;
|
||||
struct iphdr *hdr;
|
||||
struct smack_known *hskp;
|
||||
@@ -4479,7 +4472,7 @@ static int smack_inet_conn_request(const struct sock *sk, struct sk_buff *skb,
|
||||
static void smack_inet_csk_clone(struct sock *sk,
|
||||
const struct request_sock *req)
|
||||
{
|
||||
struct socket_smack *ssp = sk->sk_security;
|
||||
struct socket_smack *ssp = smack_sock(sk);
|
||||
struct smack_known *skp;
|
||||
|
||||
if (req->peer_secid != 0) {
|
||||
@@ -4511,23 +4504,13 @@ static void smack_inet_csk_clone(struct sock *sk,
|
||||
static int smack_key_alloc(struct key *key, const struct cred *cred,
|
||||
unsigned long flags)
|
||||
{
|
||||
struct smack_known **blob = smack_key(key);
|
||||
struct smack_known *skp = smk_of_task(smack_cred(cred));
|
||||
|
||||
key->security = skp;
|
||||
*blob = skp;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* smack_key_free - Clear the key security blob
|
||||
* @key: the object
|
||||
*
|
||||
* Clear the blob pointer
|
||||
*/
|
||||
static void smack_key_free(struct key *key)
|
||||
{
|
||||
key->security = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* smack_key_permission - Smack access on a key
|
||||
* @key_ref: gets to the object
|
||||
@@ -4541,6 +4524,8 @@ static int smack_key_permission(key_ref_t key_ref,
|
||||
const struct cred *cred,
|
||||
enum key_need_perm need_perm)
|
||||
{
|
||||
struct smack_known **blob;
|
||||
struct smack_known *skp;
|
||||
struct key *keyp;
|
||||
struct smk_audit_info ad;
|
||||
struct smack_known *tkp = smk_of_task(smack_cred(cred));
|
||||
@@ -4578,7 +4563,9 @@ static int smack_key_permission(key_ref_t key_ref,
|
||||
* If the key hasn't been initialized give it access so that
|
||||
* it may do so.
|
||||
*/
|
||||
if (keyp->security == NULL)
|
||||
blob = smack_key(keyp);
|
||||
skp = *blob;
|
||||
if (skp == NULL)
|
||||
return 0;
|
||||
/*
|
||||
* This should not occur
|
||||
@@ -4594,8 +4581,8 @@ static int smack_key_permission(key_ref_t key_ref,
|
||||
ad.a.u.key_struct.key = keyp->serial;
|
||||
ad.a.u.key_struct.key_desc = keyp->description;
|
||||
#endif
|
||||
rc = smk_access(tkp, keyp->security, request, &ad);
|
||||
rc = smk_bu_note("key access", tkp, keyp->security, request, rc);
|
||||
rc = smk_access(tkp, skp, request, &ad);
|
||||
rc = smk_bu_note("key access", tkp, skp, request, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -4610,11 +4597,12 @@ static int smack_key_permission(key_ref_t key_ref,
|
||||
*/
|
||||
static int smack_key_getsecurity(struct key *key, char **_buffer)
|
||||
{
|
||||
struct smack_known *skp = key->security;
|
||||
struct smack_known **blob = smack_key(key);
|
||||
struct smack_known *skp = *blob;
|
||||
size_t length;
|
||||
char *copy;
|
||||
|
||||
if (key->security == NULL) {
|
||||
if (skp == NULL) {
|
||||
*_buffer = NULL;
|
||||
return 0;
|
||||
}
|
||||
@@ -4922,10 +4910,10 @@ static int smack_inode_copy_up(struct dentry *dentry, struct cred **new)
|
||||
static int smack_inode_copy_up_xattr(struct dentry *src, const char *name)
|
||||
{
|
||||
/*
|
||||
* Return 1 if this is the smack access Smack attribute.
|
||||
* Return -ECANCELED if this is the smack access Smack attribute.
|
||||
*/
|
||||
if (strcmp(name, XATTR_NAME_SMACK) == 0)
|
||||
return 1;
|
||||
if (!strcmp(name, XATTR_NAME_SMACK))
|
||||
return -ECANCELED;
|
||||
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
@@ -5048,7 +5036,9 @@ struct lsm_blob_sizes smack_blob_sizes __ro_after_init = {
|
||||
.lbs_file = sizeof(struct smack_known *),
|
||||
.lbs_inode = sizeof(struct inode_smack),
|
||||
.lbs_ipc = sizeof(struct smack_known *),
|
||||
.lbs_key = sizeof(struct smack_known *),
|
||||
.lbs_msg_msg = sizeof(struct smack_known *),
|
||||
.lbs_sock = sizeof(struct socket_smack),
|
||||
.lbs_superblock = sizeof(struct superblock_smack),
|
||||
.lbs_xattr_count = SMACK_INODE_INIT_XATTRS,
|
||||
};
|
||||
@@ -5173,7 +5163,9 @@ static struct security_hook_list smack_hooks[] __ro_after_init = {
|
||||
LSM_HOOK_INIT(socket_getpeersec_stream, smack_socket_getpeersec_stream),
|
||||
LSM_HOOK_INIT(socket_getpeersec_dgram, smack_socket_getpeersec_dgram),
|
||||
LSM_HOOK_INIT(sk_alloc_security, smack_sk_alloc_security),
|
||||
#ifdef SMACK_IPV6_PORT_LABELING
|
||||
LSM_HOOK_INIT(sk_free_security, smack_sk_free_security),
|
||||
#endif
|
||||
LSM_HOOK_INIT(sk_clone_security, smack_sk_clone_security),
|
||||
LSM_HOOK_INIT(sock_graft, smack_sock_graft),
|
||||
LSM_HOOK_INIT(inet_conn_request, smack_inet_conn_request),
|
||||
@@ -5182,7 +5174,6 @@ static struct security_hook_list smack_hooks[] __ro_after_init = {
|
||||
/* key management security hooks */
|
||||
#ifdef CONFIG_KEYS
|
||||
LSM_HOOK_INIT(key_alloc, smack_key_alloc),
|
||||
LSM_HOOK_INIT(key_free, smack_key_free),
|
||||
LSM_HOOK_INIT(key_permission, smack_key_permission),
|
||||
LSM_HOOK_INIT(key_getsecurity, smack_key_getsecurity),
|
||||
#ifdef CONFIG_KEY_NOTIFICATIONS
|
||||
|
||||
@@ -26,8 +26,8 @@ static unsigned int smack_ip_output(void *priv,
|
||||
struct socket_smack *ssp;
|
||||
struct smack_known *skp;
|
||||
|
||||
if (sk && sk->sk_security) {
|
||||
ssp = sk->sk_security;
|
||||
if (sk) {
|
||||
ssp = smack_sock(sk);
|
||||
skp = ssp->smk_out;
|
||||
skb->secmark = skp->smk_secid;
|
||||
}
|
||||
|
||||
@@ -128,6 +128,9 @@ TEST(correct_lsm_list_modules)
|
||||
case LSM_ID_EVM:
|
||||
name = "evm";
|
||||
break;
|
||||
case LSM_ID_IPE:
|
||||
name = "ipe";
|
||||
break;
|
||||
default:
|
||||
name = "INVALID";
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user