Uploaded image for project: 'Lustre'
  1. Lustre
  2. LU-20522

recovery-small test_154a intermittently crashes

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Unresolved
    • Icon: Medium Medium
    • Lustre 2.18.0
    • Lustre 2.15.0, Lustre 2.17.0, Lustre 2.18.0
    • None
    • 3
    • 9223372036854775807

      recovery-small test_154a intermittently crashes the MDS with an out-of-bounds
      access in print_llog_hdr(), reached from lustre_swab_llog_hdr():\

         BUG: unable to handle kernel paging request at ffff9857f0a959ca
        RIP: 0010:print_llog_hdr+0x568/0x6b0 [obdclass]
        RAX: 00000000dc75d9ce RBX: ffff985714338000
        CR2: ffff9857f0a959ca
        Comm: lod0000_rec0001
        Call Trace:
         lustre_swab_llog_hdr+0x37/0x140 [obdclass]
         llog_osd_read_header+0x4b1/0xb60 [obdclass]
         llog_read_header+0x6a/0x390 [obdclass]
         llog_init_handle+0xf2/0xad0 [obdclass]
         lod_sub_prep_llog+0x581/0xcd9 [lod]
         lod_sub_recovery_thread+0xd6/0xef0 [lod]
      

      Observed in both boilpot and maloo

      https://testing.whamcloud.com/test_sets/45052dfe-8146-44f2-9908-c27f75d07a16

      https://testing.whamcloud.com/gerrit-janitor/external/crashes/boilpot-bigmem2-112-2026-07-25-04:00:02

      AI root cause:

      LLOG_HDR_TAIL() locates the header tail at llh_hdr.lrh_len - 8 bytes from the
      start of the header buffer. lustre_swab_llog_hdr() dereferences it twice
      without ever bounding lrh_len, which at that point is raw data from disk or
      from the wire:

        print_llog_hdr()       CDEBUG(D_OTHER) of LLOG_HDR_TAIL(h)->lrt_index and
                               ->lrt_len - an out-of-bounds read
        lustre_swab_llog_rec() __swab32s() of those same two fields via
                               tail = LLOG_HDR_TAIL(llh) - an out-of-bounds WRITE

      The first print_llog_hdr() call happens before the header is swabbed, so
      lrh_len is still in the peer's byte order. Even a perfectly valid
      foreign-endian header therefore reads ~2MB past an 8KB buffer:
      __swab32(8192) == 0x00200000.

      test_154a overwrites an update log catalog with /dev/urandom and expects the
      corrupt log to be rejected. About 1 random lrh_type in 4096 satisfies
      LLOG_REC_HDR_NEEDS_SWABBING() - the condition is
      (lrh_type & 0x0000f0ff) == 0x00006010 - which sends the random header into
      lustre_swab_llog_hdr() and dereferences the tail at a random multi-gigabyte
      offset. That is the intermittency.

      The registers confirm this exactly rather than by inference: the faulting
      instruction is "mov edx,[rbx+rax-4]", RAX holds the on-disk lrh_len
      0xdc75d9ce, RBX the header buffer at ffff985714338000, and
      RBX + RAX - 4 == CR2, i.e. LLOG_HDR_TAIL(h)->lrt_index precisely.

      WHY IT ONLY APPEARS RECENTLY

      The unbounded dereference dates to dc689955366c ("LU-6602 obdclass: variable
      llog chunk size", 2015, first in 2.7.56), which replaced the fixed
      &llh->llh_tail member with the lrh_len-derived LLOG_HDR_TAIL() macro. Nothing
      about the call path changed since - the lustre_swab_llog_hdr() call in
      llog_osd_read_header() is unchanged since 0d43b8d1b0 (LU-1302) and
      LLOG_REC_HDR_NEEDS_SWABBING() since the b1_5 landing.

      What is new is the exposure: nothing fed a corrupt header into that path
      until test_154a landed in 54301fe4f5 ("LU-15934 tests: add a test case for
      update llog", first in 2.15.57), and its MDS1_VERSION >= 2.15.60.2 guard
      means it only actually runs on builds from about 2.15.61 on. At 1 in 4096 per
      run, a first sighting in Sept 2024 is consistent.

      It is not test-only. lod_update_log_stale() (added in b054fcd785, "LU-16159
      lod: cancel update llogs upon recovery abort") swabs an on-disk header during
      ordinary stale update log cleanup with no length check at all, so any DNE
      filesystem that hits a damaged or truncated update log can take the same
      path. The RMF_LLOG_LOG_HDR reply swabber registered in ptlrpc/layout.c is a
      third caller, where a foreign-endian peer could reach the out-of-bounds
      write over the network; that one is hardening and has not been demonstrated.

      Note the out-of-bounds WRITE in lustre_swab_llog_rec() is the more dangerous
      half: when the computed address happens to be mapped it silently corrupts 8
      bytes of kernel memory instead of panicking, and it does not depend on
      D_OTHER being enabled.

      AI-suggested fix:

      Bound lrh_len to the range llog_init_handle() can actually allocate, and
      require a power of two, before dereferencing the tail in either place.
      Callers detect the still-unswabbed header through their existing lrh_type and
      lrh_len checks and return -EINVAL, which lod_sub_prep_llog() already handles
      by renewing the log. Fixing it in the swabber covers all three callers.

       

       

            wc-triage WC Triage
            green Oleg Drokin
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated: