From: Roberto Sassu <roberto.sassu@huaweicloud.com>
To: Fabrice Derepas <fabrice.derepas@canonical.com>,
Mimi Zohar <zohar@linux.ibm.com>,
Roberto Sassu <roberto.sassu@huawei.com>,
Dmitry Kasatkin <dmitry.kasatkin@gmail.com>
Cc: Eric Snowberg <eric.snowberg@oracle.com>,
Paul Moore <paul@paul-moore.com>,
James Morris <jmorris@namei.org>,
"Serge E. Hallyn" <serge@hallyn.com>,
linux-integrity@vger.kernel.org,
linux-security-module@vger.kernel.org,
linux-kernel@vger.kernel.org
Subject: Re: [PATCH] ima: bound line scan in ima_read_policy() to fix OOB read
Date: Wed, 26 Aug 2026 11:51:48 +0200 [thread overview]
Message-ID: <1911937d73df65f70087c6f9b52798e1a5dfb33f.camel@huaweicloud.com> (raw)
In-Reply-To: <20260814085443.1211989-1-fabrice.derepas@canonical.com>
On Fri, 2026-08-14 at 10:54 +0200, Fabrice Derepas wrote:
> ima_read_policy() loads a policy file with kernel_read_file_from_path()
> and splits it into lines with
>
> while (size > 0 && (p = strsep(&datap, "\n")))
>
> kernel_read_file() allocates the destination with vmalloc(i_size) --
> exactly i_size bytes, and writes no NUL terminator. strsep()'s scan for
> the next '\n' is not bounded by @size, so when the last line has no
> trailing newline the scan runs off the end of the buffer (CWE-125). When
> i_size is a multiple of PAGE_SIZE the allocation has no slack and the
> read walks into the vmalloc guard page and faults.
>
> Reproduced under KASAN in a VM: writing the path of a page-aligned
> policy file with no trailing newline to <securityfs>/ima/policy oopses:
>
> BUG: unable to handle page fault for address: ffffc90000032000
> #PF: supervisor read access in kernel mode
> RIP: 0010:strsep+0x7a/0xd0
> Call Trace:
> ima_write_policy+0x1f4/0x260
> vfs_write+0x16a/0x6f0
> ksys_write+0xcb/0x160
> do_syscall_64+0xe0/0x5a0
>
> This requires CAP_MAC_ADMIN (the policy file is mode 0200), but a policy
> file that does not end in a newline is an ordinary, non-malicious
> condition, so a legitimate policy load can crash the kernel.
>
> Walk the buffer with memchr() bounded by the remaining size instead of
> strsep(): terminate each line in place at its newline, and parse a
> NUL-terminated copy of a final line that has none. The explicit per-line
> accounting replaces the old "size -= rc" step, whose off-by-one
> (ima_parse_add_rule() returns strlen() + 1) made a trailing line without a
> newline fail with -EINVAL; such a policy now loads. The loop now consumes
> the buffer exactly, so the trailing "if (size) return -EINVAL" is dropped.
Looks unnecessarily complicated. I would replicate instead the same
behavior of ima_write_policy() to allocate a buffer with an additional
byte for the terminator.
Read the inode size, vmalloc() size + 1, set the terminator, and pass
the buffer to kernel_read_file_from_path().
I would still pass NULL for file_size to save ourselves from rechecking
if it changed after the kernel_read_file_from_path() call.
Thanks
Roberto
> Fixes: 7429b092811f ("ima: load policy using path")
> Assisted-by: copilot-cli:claude-opus-4-6 frama-c
> Signed-off-by: Fabrice Derepas <fabrice.derepas@canonical.com>
> ---
> Tested under KASAN (CONFIG_KASAN_GENERIC + CONFIG_KASAN_VMALLOC) in QEMU,
> loading a policy via "echo /path > <securityfs>/ima/policy":
>
> - page-aligned file, no trailing newline: unpatched -> guard-page oops
> in strsep()/ima_read_policy() (trace above); patched -> no fault, the
> load fails cleanly with -EINVAL on the (garbage) content.
> - valid policy with a trailing newline: loads before and after.
> - valid rule with no trailing newline: unpatched -> -EINVAL (the size
> underflow); patched -> loads.
>
> lib/string.o is not KASAN-instrumented, so the over-read is caught by the
> vmalloc guard page rather than a shadow report; the confirmation is the
> page-fault oops with strsep()/ima_write_policy() in the trace.
>
> security/integrity/ima/ima_fs.c | 46 ++++++++++++++++++++++++++-------
> 1 file changed, 36 insertions(+), 10 deletions(-)
>
> diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
> index 174a94740..7b530b130 100644
> --- a/security/integrity/ima/ima_fs.c
> +++ b/security/integrity/ima/ima_fs.c
> @@ -526,12 +526,10 @@ static const struct file_operations ima_ascii_measurements_staged_ops = {
> static ssize_t ima_read_policy(char *path)
> {
> void *data = NULL;
> - char *datap;
> - size_t size;
> + char *datap, *eol, *p;
> + size_t size, linelen;
> int rc, pathlen = strlen(path);
>
> - char *p;
> -
> /* remove \n */
> datap = path;
> strsep(&datap, "\n");
> @@ -546,21 +544,49 @@ static ssize_t ima_read_policy(char *path)
> rc = 0;
>
> datap = data;
> - while (size > 0 && (p = strsep(&datap, "\n"))) {
> + while (size > 0) {
> + eol = memchr(datap, '\n', size);
> + linelen = eol ? (size_t)(eol - datap) : size;
> +
> + if (eol) {
> + /* NUL-terminate the line in place, within bounds. */
> + *eol = '\0';
> + p = datap;
> + } else {
> + /*
> + * kernel_read_file_from_path() does not NUL-terminate
> + * the buffer, and it may be exactly i_size bytes long,
> + * so a string walk off the end is possible. The final
> + * line without a trailing newline has no room for a
> + * terminator; parse a terminated copy instead.
> + */
> + p = kmemdup_nul(datap, linelen, GFP_KERNEL);
> + if (!p) {
> + rc = -ENOMEM;
> + break;
> + }
> + }
> +
> pr_debug("rule: %s\n", p);
> rc = ima_parse_add_rule(p);
> + if (!eol)
> + kfree(p);
> if (rc < 0)
> break;
> - size -= rc;
> + rc = 0;
> +
> + datap += linelen;
> + size -= linelen;
> + if (eol) {
> + datap++; /* skip the newline */
> + size--;
> + }
> }
>
> vfree(data);
> if (rc < 0)
> return rc;
> - else if (size)
> - return -EINVAL;
> - else
> - return pathlen;
> + return pathlen;
> }
>
> static ssize_t ima_write_policy(struct file *file, const char __user *buf,
>
> base-commit: d58772d8520c7ef247c4b95c9bd76d3a25da9ff5
prev parent reply other threads:[~2026-08-26 10:08 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-14 8:54 [PATCH] ima: bound line scan in ima_read_policy() to fix OOB read Fabrice Derepas
2026-08-26 9:51 ` Roberto Sassu [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1911937d73df65f70087c6f9b52798e1a5dfb33f.camel@huaweicloud.com \
--to=roberto.sassu@huaweicloud.com \
--cc=dmitry.kasatkin@gmail.com \
--cc=eric.snowberg@oracle.com \
--cc=fabrice.derepas@canonical.com \
--cc=jmorris@namei.org \
--cc=linux-integrity@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-security-module@vger.kernel.org \
--cc=paul@paul-moore.com \
--cc=roberto.sassu@huawei.com \
--cc=serge@hallyn.com \
--cc=zohar@linux.ibm.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox