Skip to content

CLUBB advected-moments read loop runs one level past state%q (out-of-bounds) in clubb_intr.F90 #1590

Closed

Description

@duncanwp

Summary

With clubb_do_adv = .true., the loop that reads the 9 advected CLUBB moments out of state%q runs one level too far, reading past the end of state%q and writing past the end of a nzt_clubb-sized pbuf field.

Location

src/physics/cam/clubb_intr.F90:3098-3111 (registrations at line 633; writeback at 4284-4314)

Problem

The read loop runs k = 1, nzm_clubb, so k_cam reaches pverp, but state%q has only pver levels — an out-of-bounds read for all nine moments. It is also an out-of-bounds write for wp3_pbuf, which is registered with only nzt_clubb levels, corrupting the adjacent pbuf field. The corresponding writeback loop covers only nzt_clubb levels, confirming the read-loop bound is the one that is wrong.

Impact

Garbage surface-level turbulence moments entering CLUBB each substep, plus corruption of a neighboring physics-buffer field, whenever CLUBB moment advection is enabled. Affects prognostic state.

Suggested fix

do k = 1, nzt_clubb

Found via a systematic line-by-line scientific code review of src/physics/cam/ at commit 21a78294 (≈cam6_4_180), cross-checked against the registration and writeback bounds. This is a candidate bug identified by code inspection; it has not yet been confirmed by a model run. An issue-tracker search on 2026-06-11 found no existing report.

Activity

  1. nusbaume commented on Jun 17, 2026

    @nusbaume
    Collaborator

    Here is the other CLUBB-related issue @vlarson, @bstephens82 . It does look like there is an index error here, which would likely result in the model crashing if certain compiler debug flags were enabled, so I am happy to fix it if you concur it's a bug.

    For anyone else seeing this issue, please note that clubb_do_adv defaults to .false. in standard CAM runs, so this bug should have no impact on CESM3 simulations.

    Finally, a note to self that clubb_do_adv and other namelist logical variables should probably be initialized to .false. in the Fortran declaration, which could protect us against theoretical namelist errors.

  2. cacraigucar commented on Aug 11, 2026

    @cacraigucar
    Collaborator

    @jtruesdal - has this been fixed?

  3. jtruesdal commented on Aug 11, 2026

    @jtruesdal
    Collaborator

    @cacraigucar I'll take care of this in my PR as Adam recommends. I'll add the proper text to the summary. @adamrher the loop in question is below. I agree that the fix is to run the k loop to nzt_clubb to make the unpacking and packing of the pbuf values in the q array consistent. The surface level of the pbuf variables shouldn't be advected anyway so it make sense not to push it into the q array. I just wanted to check with you that the surface layer of these variables is being initialized and updated properly as the pverp values are used in calculations in other parts of clubb.

    if ( clubb_do_adv ) then
    
      if (macmic_it  ==  1) then
    
        !  Note that some of the moments below can be positive or negative.                                                                                                                                                                        
        !    Remove a constant that was added to prevent dynamics from clipping                                                                                                                                                                    
        !    them to prevent dynamics from making them positive.                                                                                                                                                                                   
        do k = 1, nzm_clubb
          do i = 1, ncol
            k_cam = top_lev - 1 + k
            rtpthlp_pbuf(i,k) = state_loc%q(i,k_cam,ixrtpthlp) - ( rtpthlp_const * apply_const )
            wpthlp_pbuf(i,k)  = state_loc%q(i,k_cam, ixwpthlp) - ( wpthlp_const  * apply_const )
            wprtp_pbuf(i,k)   = state_loc%q(i,k_cam,  ixwprtp) - ( wprtp_const   * apply_const )
            wp3_pbuf(i,k)     = state_loc%q(i,k_cam,    ixwp3) - ( wp3_const     * apply_const )
            wp2_pbuf(i,k)     = max(  w_tol_sqd, state_loc%q(i,k_cam,    ixwp2) )
            thlp2_pbuf(i,k)   = max( thl_tol**2, state_loc%q(i,k_cam,  ixthlp2) )
            rtp2_pbuf(i,k)    = max(  rt_tol**2, state_loc%q(i,k_cam,   ixrtp2) )
            up2_pbuf(i,k)     = max(  w_tol_sqd, state_loc%q(i,k_cam,    ixup2) )
            vp2_pbuf(i,k)     = max(  w_tol_sqd, state_loc%q(i,k_cam,    ixvp2) )
          enddo
        enddo
    
      endif
    
  4. adamrher commented on Aug 11, 2026

    @adamrher
    Collaborator

    I agree with you John. The only variable that's on the zt grid is wp3, and all others are on the zm grid. Cross-referencing with an older code base, we have always been assigning the zm pbuf variables under the clubb_do_adv blocks with the state%q array, a half level lower from where the zm variables actually reside (and omitting the surface values).

  5. cacraigucar commented on Sep 29, 2026

    @cacraigucar
    Collaborator

    @jtruesdal - is this in your CLUBB_MF PR?

  6. jtruesdal commented on Oct 7, 2026

    @jtruesdal
    Collaborator

    Add the new loop index as well as initializing the clubb logicals to false as recommended by Jesse to my clubb-mf PR #1576

  7. added a commit that references this issue on Oct 9, 2026
    bd83afc
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions