Re: [PATCH v3] ipe: add errno field to IPE policy load auditing

From: Jasjiv Singh
Date: Wed Mar 05 2025 - 18:27:49 EST




On 3/5/2025 1:23 PM, Fan Wu wrote:
> On Tue, Mar 4, 2025 at 4:04 PM Jasjiv Singh
> <jasjivsingh@xxxxxxxxxxxxxxxxxxx> wrote:
>>
>>
>>
>> On 3/3/2025 2:11 PM, Fan Wu wrote:
>>> On Fri, Feb 28, 2025 at 3:11 PM Jasjiv Singh
>>> <jasjivsingh@xxxxxxxxxxxxxxxxxxx> wrote:
>>>>
>>>> Users of IPE require a way to identify when and why an operation fails,
>>>> allowing them to both respond to violations of policy and be notified
>>>> of potentially malicious actions on their systems with respect to IPE.
>>>>
>>>> This patch introduces a new error field to the AUDIT_IPE_POLICY_LOAD event
>>>> to log policy loading failures. Currently, IPE only logs successful policy
>>>> loads, but not failures. Tracking failures is crucial to detect malicious
>>>> attempts and ensure a complete audit trail for security events.
>>>>
>>>> The new error field will capture the following error codes:
>>>>
>>>> * 0: no error
>>>> * -EPERM: Insufficient permission
>>>> * -EEXIST: Same name policy already deployed
>>>> * -EBADMSG: policy is invalid
>>>> * -ENOMEM: out of memory (OOM)
>>>> * -ERANGE: policy version number overflow
>>>> * -EINVAL: policy version parsing error
>>>>
>>>
>>> These error codes are not exhaustive. We recently introduced the
>>> secondary keyring and platform keyring to sign policy so the policy
>>> loading could return -ENOKEY or -EKEYREJECT. And also the update
>>> policy can return -ESTALE when the policy version is old.
>>> This is my fault that I forgot we should also update the documentation
>>> of the newly introduced error codes. Could you please go through the
>>> whole loading code and find all possible error codes? Also this is a
>>> good chance to update the current stale function documents.
>>>
>>> ...
>>>
>>
>> So, I looked into error codes when the policy loads. In ipe_new_policy,
>> the verify_pkcs7_signature can return a lot of errno codes (ex: ENOKEY,
>> EKEYREJECTED, EBADMSG, etc.) while parsing the pkcs7 and other functions
>> as well. Also, In ipe_new_policyfs_node used in new_policy(), I see the same
>> issue with securityfs_create_dir and securityfs_create_file as they
>> return the errno directly from API to. So, what should we return?
>
> I think the key here is we need to document the errors in the ipe's
> context. For example, ENOKEY means the key used to sign the ipe policy
> is not found in the keyring, EKEYREJECTED means ipe signature
> verification failed. If an error does not have specific meaning for
> ipe then probably we don't need to document it.
>
>>
>> For other functions: I have complied the errno list:
>>
>> * -ENOENT: Policy is not found while updating
>
> This one means policy was deleted while updating, this only happens
> the update happened just after the policy deletion.
>
>> * -EEXIST: Same name policy already deployed
>> * -ERANGE: Policy version number overflow
>> * -EINVAL: Policy version parsing error
>> * -EPERM: Insufficient permission
>> * -ESTALE: Policy version is old
>
> Maybe make this one clearer, how about trying to update an ipe policy
> with an older version policy.
>
> -Fan

Thanks, I have compiled the list based on your comments.

* -ENOKEY: Key used to sign the IPE policy not found in the keyring
* -ESTALE: Attempting to update an IPE policy with an older version
* -EKEYREJECTED: IPE signature verification failed
* -ENOENT: Policy was deleted while updating
* -EEXIST: Same name policy already deployed
* -ERANGE: Policy version number overflow
* -EINVAL: Policy version parsing error
* -EPERM: Insufficient permission
* -ENOMEM: Out of memory (OOM)
* -EBADMSG: Policy is invalid

How does that that look? I will update the documentation with this list
in the next patch along with suggestions you mentioned.


Moving the memdup failure discussion here:

>>> diff --git a/security/ipe/fs.c b/security/ipe/fs.c
>>> index 5b6d19fb844a..da51264a1d0f 100644
>>> --- a/security/ipe/fs.c
>>> +++ b/security/ipe/fs.c
>>> @@ -141,12 +141,16 @@ static ssize_t new_policy(struct file *f, const char __user *data,
>>> char *copy = NULL;
>>> int rc = 0;
>>>
>>> - if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN))
>>> - return -EPERM;
>>> + if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN)) {
>>> + rc = -EPERM;
>>> + goto out;
>>> + }
>>>
>>> copy = memdup_user_nul(data, len);
>>> - if (IS_ERR(copy))
>>> - return PTR_ERR(copy);
>>> + if (IS_ERR(copy)) {
>>> + rc = PTR_ERR(copy);
>>> + goto out;
>>> + }
>>>
>>> p = ipe_new_policy(NULL, 0, copy, len);
>>> if (IS_ERR(p)) {
>>> @@ -161,8 +165,10 @@ static ssize_t new_policy(struct file *f, const char __user *data,
>>> ipe_audit_policy_load(p);
>>>
>>> out:
>>> - if (rc < 0)
>>> + if (rc < 0) {
>>> ipe_free_policy(p);
>>> + ipe_audit_policy_load(ERR_PTR(rc));
>>> + }
>>> kfree(copy);
>>> return (rc < 0) ? rc : len;
>>> }
>>
>> In case of memdup fail, the kfree(copy) will be called with the error
>> pointer. Also how about refactor the code like
>>
>> ipe_audit_policy_load(p);
>> kfree(copy);
>>
>> return len;
>> err:
>> ipe_audit_policy_load(ERR_PTR(rc));
>> ipe_free_policy(p);
>>
>> return rc;

Another issue I was thinking about that is what if memdup works but then
ipe_new_policy fails, then we still need to call kfree but the above code
mentioned by you will not do that.

I think we can just use "copy = NULL;" after recording the rc value from it,
instead of the suggested code. For examples, we can look at selinux.

-Jasjiv