diff mbox series

[v3,bpf-next,01/10] lib/buildid: harden build ID parsing logic

Message ID 20240730203914.1182569-2-andrii@kernel.org (mailing list archive)
State Superseded
Delegated to: BPF
Headers show
Series Harden and extend ELF build ID parsing logic | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for bpf-next, async
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 42 this patch: 42
netdev/build_tools success No tools touched, skip
netdev/cc_maintainers fail 3 blamed authors not CCed: ast@kernel.org songliubraving@fb.com jolsa@kernel.org; 3 maintainers not CCed: ast@kernel.org songliubraving@fb.com jolsa@kernel.org
netdev/build_clang success Errors and warnings before: 43 this patch: 43
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success Fixes tag looks correct
netdev/build_allmodconfig_warn success Errors and warnings before: 43 this patch: 43
netdev/checkpatch warning WARNING: const array should probably be static const WARNING: line length of 83 exceeds 80 columns WARNING: line length of 98 exceeds 80 columns
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0
bpf/vmtest-bpf-next-VM_Test-0 success Logs for Lint
bpf/vmtest-bpf-next-VM_Test-1 success Logs for ShellCheck
bpf/vmtest-bpf-next-VM_Test-2 success Logs for Unittests
bpf/vmtest-bpf-next-VM_Test-5 success Logs for aarch64-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-3 success Logs for Validate matrix.py
bpf/vmtest-bpf-next-VM_Test-4 success Logs for aarch64-gcc / build / build for aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-12 success Logs for s390x-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-10 success Logs for aarch64-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-11 success Logs for s390x-gcc / build / build for s390x with gcc
bpf/vmtest-bpf-next-VM_Test-17 success Logs for s390x-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-18 success Logs for set-matrix
bpf/vmtest-bpf-next-VM_Test-19 success Logs for x86_64-gcc / build / build for x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-20 success Logs for x86_64-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-28 success Logs for x86_64-llvm-17 / build / build for x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-29 success Logs for x86_64-llvm-17 / build-release / build for x86_64 with llvm-17-O2
bpf/vmtest-bpf-next-VM_Test-34 success Logs for x86_64-llvm-17 / veristat
bpf/vmtest-bpf-next-VM_Test-35 success Logs for x86_64-llvm-18 / build / build for x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-36 success Logs for x86_64-llvm-18 / build-release / build for x86_64 with llvm-18-O2
bpf/vmtest-bpf-next-VM_Test-42 success Logs for x86_64-llvm-18 / veristat
bpf/vmtest-bpf-next-VM_Test-6 success Logs for aarch64-gcc / test (test_maps, false, 360) / test_maps on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-9 success Logs for aarch64-gcc / test (test_verifier, false, 360) / test_verifier on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-13 fail Logs for s390x-gcc / test (test_maps, false, 360) / test_maps on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-16 success Logs for s390x-gcc / test (test_verifier, false, 360) / test_verifier on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-7 success Logs for aarch64-gcc / test (test_progs, false, 360) / test_progs on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-15 success Logs for s390x-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-8 success Logs for aarch64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-30 success Logs for x86_64-llvm-17 / test (test_maps, false, 360) / test_maps on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-31 success Logs for x86_64-llvm-17 / test (test_progs, false, 360) / test_progs on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-32 success Logs for x86_64-llvm-17 / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-33 success Logs for x86_64-llvm-17 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-21 success Logs for x86_64-gcc / test (test_maps, false, 360) / test_maps on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-22 success Logs for x86_64-gcc / test (test_progs, false, 360) / test_progs on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-23 success Logs for x86_64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-24 success Logs for x86_64-gcc / test (test_progs_no_alu32_parallel, true, 30) / test_progs_no_alu32_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-25 success Logs for x86_64-gcc / test (test_progs_parallel, true, 30) / test_progs_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-26 success Logs for x86_64-gcc / test (test_verifier, false, 360) / test_verifier on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-27 success Logs for x86_64-gcc / veristat / veristat on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-37 success Logs for x86_64-llvm-18 / test (test_maps, false, 360) / test_maps on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-38 success Logs for x86_64-llvm-18 / test (test_progs, false, 360) / test_progs on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-39 success Logs for x86_64-llvm-18 / test (test_progs_cpuv4, false, 360) / test_progs_cpuv4 on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-40 success Logs for x86_64-llvm-18 / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-41 success Logs for x86_64-llvm-18 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-18
bpf/vmtest-bpf-next-PR fail PR summary
bpf/vmtest-bpf-next-VM_Test-14 success Logs for s390x-gcc / test (test_progs, false, 360) / test_progs on s390x with gcc

Commit Message

Andrii Nakryiko July 30, 2024, 8:39 p.m. UTC
Harden build ID parsing logic, adding explicit READ_ONCE() where it's
important to have a consistent value read and validated just once.

Fixes tag below points to the code that moved this code into
lib/buildid.c, and then subsequently was used in perf subsystem, making
this code exposed to perf_event_open() users in v5.12+.

Cc: stable@vger.kernel.org
Cc: Jann Horn <jannh@google.com>
Suggested-by: Andi Kleen <ak@linux.intel.com>
Fixes: bd7525dacd7e ("bpf: Move stack_map_get_build_id into lib")
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
---
 lib/buildid.c | 51 +++++++++++++++++++++++++++------------------------
 1 file changed, 27 insertions(+), 24 deletions(-)

Comments

Andi Kleen July 31, 2024, 4:04 a.m. UTC | #1
>  	while (note_offs + sizeof(Elf32_Nhdr) < note_size) {
>  		Elf32_Nhdr *nhdr = (Elf32_Nhdr *)(note_start + note_offs);
>  
> +		name_sz = READ_ONCE(nhdr->n_namesz);
> +		desc_sz = READ_ONCE(nhdr->n_descsz);
>  		if (nhdr->n_type == BUILD_ID &&
> -		    nhdr->n_namesz == sizeof("GNU") &&
> -		    !strcmp((char *)(nhdr + 1), "GNU") &&
> -		    nhdr->n_descsz > 0 &&
> -		    nhdr->n_descsz <= BUILD_ID_SIZE_MAX) {
> -			memcpy(build_id,
> -			       note_start + note_offs +
> -			       ALIGN(sizeof("GNU"), 4) + sizeof(Elf32_Nhdr),
> -			       nhdr->n_descsz);
> -			memset(build_id + nhdr->n_descsz, 0,
> -			       BUILD_ID_SIZE_MAX - nhdr->n_descsz);
> +		    name_sz == note_name_sz &&
> +		    strcmp((char *)(nhdr + 1), note_name) == 0 &&

Doesn't the strcmp need a boundary check to be inside note_size too?

Other it may read into the next page, which could be unmapped, causing a fault.
Given it's unlikely that this happen, and the end has guard pages,
but there are some users of set_memory_np.

You could just move the later checks earlier.

The rest looks good to me.

-Andi
Andrii Nakryiko July 31, 2024, 9:54 p.m. UTC | #2
On Tue, Jul 30, 2024 at 9:05 PM Andi Kleen <ak@linux.intel.com> wrote:
>
> >       while (note_offs + sizeof(Elf32_Nhdr) < note_size) {
> >               Elf32_Nhdr *nhdr = (Elf32_Nhdr *)(note_start + note_offs);
> >
> > +             name_sz = READ_ONCE(nhdr->n_namesz);
> > +             desc_sz = READ_ONCE(nhdr->n_descsz);
> >               if (nhdr->n_type == BUILD_ID &&
> > -                 nhdr->n_namesz == sizeof("GNU") &&
> > -                 !strcmp((char *)(nhdr + 1), "GNU") &&
> > -                 nhdr->n_descsz > 0 &&
> > -                 nhdr->n_descsz <= BUILD_ID_SIZE_MAX) {
> > -                     memcpy(build_id,
> > -                            note_start + note_offs +
> > -                            ALIGN(sizeof("GNU"), 4) + sizeof(Elf32_Nhdr),
> > -                            nhdr->n_descsz);
> > -                     memset(build_id + nhdr->n_descsz, 0,
> > -                            BUILD_ID_SIZE_MAX - nhdr->n_descsz);
> > +                 name_sz == note_name_sz &&
> > +                 strcmp((char *)(nhdr + 1), note_name) == 0 &&
>
> Doesn't the strcmp need a boundary check to be inside note_size too?
>
> Other it may read into the next page, which could be unmapped, causing a fault.
> Given it's unlikely that this happen, and the end has guard pages,
> but there are some users of set_memory_np.
>
> You could just move the later checks earlier.

Yep, good catch! I'll move the overflow check and will add a note_size
check to it, thanks!

>
> The rest looks good to me.
>
> -Andi
>
Jann Horn Aug. 7, 2024, 3:11 p.m. UTC | #3
+Matthew and fsdevel list for pagecache question

On Tue, Jul 30, 2024 at 10:39 PM Andrii Nakryiko <andrii@kernel.org> wrote:
> Harden build ID parsing logic, adding explicit READ_ONCE() where it's
> important to have a consistent value read and validated just once.
>
> Fixes tag below points to the code that moved this code into
> lib/buildid.c, and then subsequently was used in perf subsystem, making
> this code exposed to perf_event_open() users in v5.12+.

One thing that still seems dodgy to me with this patch applied is the
call from build_id_parse() to find_get_page(), followed by reading the
page contents. My understanding of the page cache (which might be
incorrect) is that find_get_page() can return a page whose contents
have not been initialized yet, and you're supposed to check for
PageUptodate() or something like that before reading from it.

Maybe Matthew can check if I understood that right?


Also, it might be a good idea to liberally spray READ_ONCE() around
all the remaining unannotated shared memory accesses in
build_id_parse(), get_build_id_32(), get_build_id_64() and
parse_build_id_buf().

> Cc: stable@vger.kernel.org
> Cc: Jann Horn <jannh@google.com>
> Suggested-by: Andi Kleen <ak@linux.intel.com>
> Fixes: bd7525dacd7e ("bpf: Move stack_map_get_build_id into lib")
> Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
> ---
>  lib/buildid.c | 51 +++++++++++++++++++++++++++------------------------
>  1 file changed, 27 insertions(+), 24 deletions(-)
>
> diff --git a/lib/buildid.c b/lib/buildid.c
> index e02b5507418b..d21d86f6c19a 100644
> --- a/lib/buildid.c
> +++ b/lib/buildid.c
> @@ -18,28 +18,29 @@ static int parse_build_id_buf(unsigned char *build_id,
>                               const void *note_start,
>                               Elf32_Word note_size)
>  {
> +       const char note_name[] = "GNU";
> +       const size_t note_name_sz = sizeof(note_name);
>         Elf32_Word note_offs = 0, new_offs;
> +       u32 name_sz, desc_sz;
> +       const char *data;
>
>         while (note_offs + sizeof(Elf32_Nhdr) < note_size) {
>                 Elf32_Nhdr *nhdr = (Elf32_Nhdr *)(note_start + note_offs);
>
> +               name_sz = READ_ONCE(nhdr->n_namesz);
> +               desc_sz = READ_ONCE(nhdr->n_descsz);
>                 if (nhdr->n_type == BUILD_ID &&
> -                   nhdr->n_namesz == sizeof("GNU") &&
> -                   !strcmp((char *)(nhdr + 1), "GNU") &&
> -                   nhdr->n_descsz > 0 &&
> -                   nhdr->n_descsz <= BUILD_ID_SIZE_MAX) {
> -                       memcpy(build_id,
> -                              note_start + note_offs +
> -                              ALIGN(sizeof("GNU"), 4) + sizeof(Elf32_Nhdr),
> -                              nhdr->n_descsz);
> -                       memset(build_id + nhdr->n_descsz, 0,
> -                              BUILD_ID_SIZE_MAX - nhdr->n_descsz);
> +                   name_sz == note_name_sz &&
> +                   strcmp((char *)(nhdr + 1), note_name) == 0 &&
> +                   desc_sz > 0 && desc_sz <= BUILD_ID_SIZE_MAX) {
> +                       data = note_start + note_offs + ALIGN(note_name_sz, 4);

I don't think we have any guarantee here that this addition won't
result in an OOB pointer?

> +                       memcpy(build_id, data, desc_sz);

I think this can access OOB data (because "data" can already be OOB
and because "desc_sz" hasn't been checked against the amount of
remaining space in the page).

> +                       memset(build_id + desc_sz, 0, BUILD_ID_SIZE_MAX - desc_sz);
>                         if (size)
> -                               *size = nhdr->n_descsz;
> +                               *size = desc_sz;
>                         return 0;
>                 }
> -               new_offs = note_offs + sizeof(Elf32_Nhdr) +
> -                       ALIGN(nhdr->n_namesz, 4) + ALIGN(nhdr->n_descsz, 4);
> +               new_offs = note_offs + sizeof(Elf32_Nhdr) + ALIGN(name_sz, 4) + ALIGN(desc_sz, 4);
>                 if (new_offs <= note_offs)  /* overflow */
>                         break;

You check whether "new_offs" has wrapped here, but then on the next
loop iteration, you check for "note_offs + sizeof(Elf32_Nhdr) <
note_size". So if new_offs is 0xffffffff at this point, then I think
the overflow check here will be passed, the loop condition will be
true on 32-bit kernels (on 64-bit kernels it won't be because the
addition happens on 64-bit numbers thanks to sizeof()), and "nhdr"
will point in front of the note?

>                 note_offs = new_offs;
Andrii Nakryiko Aug. 7, 2024, 4:47 p.m. UTC | #4
On Wed, Aug 7, 2024 at 8:12 AM Jann Horn <jannh@google.com> wrote:
>
> +Matthew and fsdevel list for pagecache question
>
> On Tue, Jul 30, 2024 at 10:39 PM Andrii Nakryiko <andrii@kernel.org> wrote:
> > Harden build ID parsing logic, adding explicit READ_ONCE() where it's
> > important to have a consistent value read and validated just once.
> >
> > Fixes tag below points to the code that moved this code into
> > lib/buildid.c, and then subsequently was used in perf subsystem, making
> > this code exposed to perf_event_open() users in v5.12+.
>
> One thing that still seems dodgy to me with this patch applied is the
> call from build_id_parse() to find_get_page(), followed by reading the
> page contents. My understanding of the page cache (which might be
> incorrect) is that find_get_page() can return a page whose contents
> have not been initialized yet, and you're supposed to check for
> PageUptodate() or something like that before reading from it.
>
> Maybe Matthew can check if I understood that right?
>
>
> Also, it might be a good idea to liberally spray READ_ONCE() around
> all the remaining unannotated shared memory accesses in
> build_id_parse(), get_build_id_32(), get_build_id_64() and
> parse_build_id_buf().

Andi was against that, so I kept READ_ONCE() only where strictly
necessary, AFAICT.

>
> > Cc: stable@vger.kernel.org
> > Cc: Jann Horn <jannh@google.com>
> > Suggested-by: Andi Kleen <ak@linux.intel.com>
> > Fixes: bd7525dacd7e ("bpf: Move stack_map_get_build_id into lib")
> > Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
> > ---
> >  lib/buildid.c | 51 +++++++++++++++++++++++++++------------------------
> >  1 file changed, 27 insertions(+), 24 deletions(-)
> >
> > diff --git a/lib/buildid.c b/lib/buildid.c
> > index e02b5507418b..d21d86f6c19a 100644
> > --- a/lib/buildid.c
> > +++ b/lib/buildid.c
> > @@ -18,28 +18,29 @@ static int parse_build_id_buf(unsigned char *build_id,
> >                               const void *note_start,
> >                               Elf32_Word note_size)
> >  {
> > +       const char note_name[] = "GNU";
> > +       const size_t note_name_sz = sizeof(note_name);
> >         Elf32_Word note_offs = 0, new_offs;
> > +       u32 name_sz, desc_sz;
> > +       const char *data;
> >
> >         while (note_offs + sizeof(Elf32_Nhdr) < note_size) {
> >                 Elf32_Nhdr *nhdr = (Elf32_Nhdr *)(note_start + note_offs);
> >
> > +               name_sz = READ_ONCE(nhdr->n_namesz);
> > +               desc_sz = READ_ONCE(nhdr->n_descsz);
> >                 if (nhdr->n_type == BUILD_ID &&
> > -                   nhdr->n_namesz == sizeof("GNU") &&
> > -                   !strcmp((char *)(nhdr + 1), "GNU") &&
> > -                   nhdr->n_descsz > 0 &&
> > -                   nhdr->n_descsz <= BUILD_ID_SIZE_MAX) {
> > -                       memcpy(build_id,
> > -                              note_start + note_offs +
> > -                              ALIGN(sizeof("GNU"), 4) + sizeof(Elf32_Nhdr),
> > -                              nhdr->n_descsz);
> > -                       memset(build_id + nhdr->n_descsz, 0,
> > -                              BUILD_ID_SIZE_MAX - nhdr->n_descsz);
> > +                   name_sz == note_name_sz &&
> > +                   strcmp((char *)(nhdr + 1), note_name) == 0 &&
> > +                   desc_sz > 0 && desc_sz <= BUILD_ID_SIZE_MAX) {
> > +                       data = note_start + note_offs + ALIGN(note_name_sz, 4);
>
> I don't think we have any guarantee here that this addition won't
> result in an OOB pointer?
>
> > +                       memcpy(build_id, data, desc_sz);
>
> I think this can access OOB data (because "data" can already be OOB
> and because "desc_sz" hasn't been checked against the amount of
> remaining space in the page).

Andi already pointed this out and I fixed it locally, thanks.

>
> > +                       memset(build_id + desc_sz, 0, BUILD_ID_SIZE_MAX - desc_sz);
> >                         if (size)
> > -                               *size = nhdr->n_descsz;
> > +                               *size = desc_sz;
> >                         return 0;
> >                 }
> > -               new_offs = note_offs + sizeof(Elf32_Nhdr) +
> > -                       ALIGN(nhdr->n_namesz, 4) + ALIGN(nhdr->n_descsz, 4);
> > +               new_offs = note_offs + sizeof(Elf32_Nhdr) + ALIGN(name_sz, 4) + ALIGN(desc_sz, 4);
> >                 if (new_offs <= note_offs)  /* overflow */
> >                         break;
>
> You check whether "new_offs" has wrapped here, but then on the next
> loop iteration, you check for "note_offs + sizeof(Elf32_Nhdr) <
> note_size". So if new_offs is 0xffffffff at this point, then I think
> the overflow check here will be passed, the loop condition will be
> true on 32-bit kernels (on 64-bit kernels it won't be because the
> addition happens on 64-bit numbers thanks to sizeof()), and "nhdr"
> will point in front of the note?

Correct, and so I moved this new_offs calculation and overflow check
to the beginning of the loop, which I think should capture this issue.

For the while() condition itself I have:

        if (check_add_overflow(note_offs, note_size, &note_end))
                return -EINVAL;

        while (note_offs < note_end - sizeof(Elf32_Nhdr) - note_name_sz) {
            ...
        }

I'll try to post an updated version soon-ish, have been waiting for mm
folks feedback before posting a new version.

>
> >                 note_offs = new_offs;
diff mbox series

Patch

diff --git a/lib/buildid.c b/lib/buildid.c
index e02b5507418b..d21d86f6c19a 100644
--- a/lib/buildid.c
+++ b/lib/buildid.c
@@ -18,28 +18,29 @@  static int parse_build_id_buf(unsigned char *build_id,
 			      const void *note_start,
 			      Elf32_Word note_size)
 {
+	const char note_name[] = "GNU";
+	const size_t note_name_sz = sizeof(note_name);
 	Elf32_Word note_offs = 0, new_offs;
+	u32 name_sz, desc_sz;
+	const char *data;
 
 	while (note_offs + sizeof(Elf32_Nhdr) < note_size) {
 		Elf32_Nhdr *nhdr = (Elf32_Nhdr *)(note_start + note_offs);
 
+		name_sz = READ_ONCE(nhdr->n_namesz);
+		desc_sz = READ_ONCE(nhdr->n_descsz);
 		if (nhdr->n_type == BUILD_ID &&
-		    nhdr->n_namesz == sizeof("GNU") &&
-		    !strcmp((char *)(nhdr + 1), "GNU") &&
-		    nhdr->n_descsz > 0 &&
-		    nhdr->n_descsz <= BUILD_ID_SIZE_MAX) {
-			memcpy(build_id,
-			       note_start + note_offs +
-			       ALIGN(sizeof("GNU"), 4) + sizeof(Elf32_Nhdr),
-			       nhdr->n_descsz);
-			memset(build_id + nhdr->n_descsz, 0,
-			       BUILD_ID_SIZE_MAX - nhdr->n_descsz);
+		    name_sz == note_name_sz &&
+		    strcmp((char *)(nhdr + 1), note_name) == 0 &&
+		    desc_sz > 0 && desc_sz <= BUILD_ID_SIZE_MAX) {
+			data = note_start + note_offs + ALIGN(note_name_sz, 4);
+			memcpy(build_id, data, desc_sz);
+			memset(build_id + desc_sz, 0, BUILD_ID_SIZE_MAX - desc_sz);
 			if (size)
-				*size = nhdr->n_descsz;
+				*size = desc_sz;
 			return 0;
 		}
-		new_offs = note_offs + sizeof(Elf32_Nhdr) +
-			ALIGN(nhdr->n_namesz, 4) + ALIGN(nhdr->n_descsz, 4);
+		new_offs = note_offs + sizeof(Elf32_Nhdr) + ALIGN(name_sz, 4) + ALIGN(desc_sz, 4);
 		if (new_offs <= note_offs)  /* overflow */
 			break;
 		note_offs = new_offs;
@@ -71,7 +72,7 @@  static int get_build_id_32(const void *page_addr, unsigned char *build_id,
 {
 	Elf32_Ehdr *ehdr = (Elf32_Ehdr *)page_addr;
 	Elf32_Phdr *phdr;
-	int i;
+	__u32 i, phnum;
 
 	/*
 	 * FIXME
@@ -80,9 +81,10 @@  static int get_build_id_32(const void *page_addr, unsigned char *build_id,
 	 */
 	if (ehdr->e_phoff != sizeof(Elf32_Ehdr))
 		return -EINVAL;
+
+	phnum = READ_ONCE(ehdr->e_phnum);
 	/* only supports phdr that fits in one page */
-	if (ehdr->e_phnum >
-	    (PAGE_SIZE - sizeof(Elf32_Ehdr)) / sizeof(Elf32_Phdr))
+	if (phnum > (PAGE_SIZE - sizeof(Elf32_Ehdr)) / sizeof(Elf32_Phdr))
 		return -EINVAL;
 
 	phdr = (Elf32_Phdr *)(page_addr + sizeof(Elf32_Ehdr));
@@ -90,8 +92,8 @@  static int get_build_id_32(const void *page_addr, unsigned char *build_id,
 	for (i = 0; i < ehdr->e_phnum; ++i) {
 		if (phdr[i].p_type == PT_NOTE &&
 		    !parse_build_id(page_addr, build_id, size,
-				    page_addr + phdr[i].p_offset,
-				    phdr[i].p_filesz))
+				    page_addr + READ_ONCE(phdr[i].p_offset),
+				    READ_ONCE(phdr[i].p_filesz)))
 			return 0;
 	}
 	return -EINVAL;
@@ -103,7 +105,7 @@  static int get_build_id_64(const void *page_addr, unsigned char *build_id,
 {
 	Elf64_Ehdr *ehdr = (Elf64_Ehdr *)page_addr;
 	Elf64_Phdr *phdr;
-	int i;
+	__u32 i, phnum;
 
 	/*
 	 * FIXME
@@ -112,18 +114,19 @@  static int get_build_id_64(const void *page_addr, unsigned char *build_id,
 	 */
 	if (ehdr->e_phoff != sizeof(Elf64_Ehdr))
 		return -EINVAL;
+
+	phnum = READ_ONCE(ehdr->e_phnum);
 	/* only supports phdr that fits in one page */
-	if (ehdr->e_phnum >
-	    (PAGE_SIZE - sizeof(Elf64_Ehdr)) / sizeof(Elf64_Phdr))
+	if (phnum > (PAGE_SIZE - sizeof(Elf64_Ehdr)) / sizeof(Elf64_Phdr))
 		return -EINVAL;
 
 	phdr = (Elf64_Phdr *)(page_addr + sizeof(Elf64_Ehdr));
 
-	for (i = 0; i < ehdr->e_phnum; ++i) {
+	for (i = 0; i < phnum; ++i) {
 		if (phdr[i].p_type == PT_NOTE &&
 		    !parse_build_id(page_addr, build_id, size,
-				    page_addr + phdr[i].p_offset,
-				    phdr[i].p_filesz))
+				    page_addr + READ_ONCE(phdr[i].p_offset),
+				    READ_ONCE(phdr[i].p_filesz)))
 			return 0;
 	}
 	return -EINVAL;