Linux filesystem development
 help / color / mirror / Atom feed
From: Kees Cook <kees@kernel.org>
To: Konstantin Khorenko <khorenko@virtuozzo.com>
Cc: Alexander Viro <viro@zeniv.linux.org.uk>,
	Christian Brauner <brauner@kernel.org>, Jan Kara <jack@suse.cz>,
	Paul Burton <paulburton@kernel.org>,
	linux-fsdevel@vger.kernel.org, linux-mm@kvack.org,
	linux-kernel@vger.kernel.org
Subject: Re: [PATCH] binfmt_elf: return the real error when program headers cannot be loaded
Date: Wed, 14 Oct 2026 21:48:17 -0700	[thread overview]
Message-ID: <202610142140.D483AE26DF@keescook> (raw)
In-Reply-To: <20261014-b4-prep-vstor-149847-binfmt_elf-enomem-load-elf-phdrs-v1-1-f39c74d55e42@virtuozzo.com>

On Wed, Oct 14, 2026 at 02:30:33PM +0200, Konstantin Khorenko wrote:
> Problem
> =======
> 
> load_elf_phdrs() returns NULL on any failure, and load_elf_binary()
> turns that into a fixed error: -ENOEXEC for the executable, -ELIBBAD
> for its interpreter. The real cause is lost:
> 
>   cause                                  really      execve() returns
>   -------------------------------------  ----------  ----------------
>   bad e_phentsize, e_phnum 0 or too big  bad file    ENOEXEC (right)
>   file ends before the header table      bad file    ENOEXEC (right)
>   kmalloc() of the table fails           ENOMEM      ENOEXEC (wrong)
>   kernel_read() fails                    EIO/ENOMEM  ENOEXEC (wrong)
> 
> For the interpreter every row gives ELIBBAD.
> 
> Consequences
> ============
> 
> ENOEXEC says the file is not an executable, which is a property of the
> file, not a transient failure:
> 
> - glibc's execvp() and execlp() take ENOEXEC as "not a binary" and run
>   the ELF file with /bin/sh, which reports a binary it cannot execute
>   instead of the ENOMEM or EIO.
> - search_binary_handler() tries the next binary format instead of
>   returning the error.

Isn't that what we want? If one handler can't load it, move on to find
one that does.

> - For the interpreter the caller gets ELIBBAD, "Accessing a corrupted
>   shared library", about an ld.so that is fine.
> - A caller that would retry on ENOMEM or report EIO gives up on the
>   file instead.
> 
> When it happens
> ===============
> 
> - The allocation fails under fault injection. Since commit
>   8030790477e8 ("binfmt_elf: remove the 4k limitation of program header
>   size") the table may be up to 64K; above 32K that is an order-4
>   allocation on 4K-page kernels, which can fail on a fragmented system.

I would not want to risk changing established userspace error message
interactions to make fault injection happy.

> - The read fails with an I/O error, or with -ENOMEM when the page cache
>   cannot take the pages.

This is kind of a bad failure case -- I don't think the "right" error
code is going to help much there. ;)

> 
> Where it came from
> ==================
> 
> - Until commit 6a8d38945cf4 ("binfmt_elf: Hoist ELF program header
>   loading to a function") load_elf_binary() returned -ENOMEM and the
>   read error. That commit documented an ERR_PTR return but returned
>   NULL; commit cfc46ca4fdca later fixed the comment, not the code.
> - Commit a9d9ef133f44 ("binfmt_elf: load interpreter program headers
>   earlier") gave the interpreter's headers the same NULL check, and with
>   it -ELIBBAD.

So it's been like this for 12 years? :)

> [...]
>   kernel                errors returned by execve()
>   7.3-rc7               ENOMEM 17, ENOEXEC 1, ELIBBAD 1, E2BIG 2
>   7.3-rc7 + this patch  ENOMEM 19, E2BIG 2

... so there are _fewer_ distinct failure codes with the patch?

> +	/*
> +	 * Read in the program headers. A read that fails part way returns
> +	 * what it got, so read on to see the error or the end of the file.
> +	 */
> +	pos = elf_ex->e_phoff;
> +	for (done = 0; done < size; done += rv) {
> +		rv = kernel_read(elf_file, (char *)elf_phdata + done,
> +				 size - done, &pos);
> +		if (rv <= 0) {
> +			kfree(elf_phdata);
> +			return ERR_PTR(rv ? rv : -ENOEXEC);
> +		}
>  	}

This is a behavioral change mixed into the errno handling change to
support partial reads??

I could be convinced that errnos need to be fixed, but the rationale
here doesn't really make me think it's worth potential disruption to
userspace. I'd need a pretty comprehensive survey of what would change
there and then a clean patch that made zero other behavior changes...

-Kees

-- 
Kees Cook

      reply	other threads:[~2026-10-15  4:48 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-14 12:30 [PATCH] binfmt_elf: return the real error when program headers cannot be loaded Konstantin Khorenko
2026-10-15  4:48 ` Kees Cook [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=202610142140.D483AE26DF@keescook \
    --to=kees@kernel.org \
    --cc=brauner@kernel.org \
    --cc=jack@suse.cz \
    --cc=khorenko@virtuozzo.com \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=paulburton@kernel.org \
    --cc=viro@zeniv.linux.org.uk \
    /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