Skip to content

OS command injection: data-model stream paths/filenames reach /bin/cp and rm via system() (shr_file_mod.F90, shr_dmodel_mod.F90) #5008

Description

@Champollion9012

Labels: bug, security

Security report — arbitrary OS command execution as the running user, proven end-to-end on a
deployed 512-rank cesm.exe. Please triage privately; happy to move to a security contact.


What happened?

The share-code file utilities build shell command lines by string-concatenating file/stream-derived
paths
into /bin/cp, msrcp, and rm, and run them through a shell
(src/share/util/shr_sys_mod.F90:83 call execute_command_line(str)/bin/sh -c). The operands come
from a data-model stream descriptor, so any shell metacharacter in a <filePath>/<fileNames>
entry is executed as a command.

src/share/util/shr_file_mod.F90shr_file_get (get, ~line 274/280), shr_file_put (put, ~line 164/167):

rfn = rem_fn ; if (rem_fn(1:3)=="cp:") rfn = rem_fn(4:)
cmd = '/bin/cp -f '//trim(rfn)//' '//trim(loc_fn)     ! :274  (put mirror + optional ' && /bin/rm -f '//loc_fn)
call shr_sys_system(trim(cmd),rcode)                  ! :280  (mss:/msrcp branch same: :179 / :288)

src/share/streams/shr_dmodel_mod.F90 (~line 711):

localCopy = (shr_file_queryPrefix(path) /= shr_file_noPrefix)     ! stream path has cp:/mss:
if (localCopy .and. rmOldFile .and. fileExists) &
   call shr_sys_system(" rm "//trim(fn_prev),rCode)              ! fn_prev = a stream <fileNames> entry

The data models (DOCN/DATM/DICE/DLND/DROF) read a stream text descriptor at init and, for any stream
whose <filePath> carries a cp:/mss: prefix, call shr_file_get with the stream's own path/filename
strings — unconditionally on the master task:

src/share/streams/shr_strdata_mod.F90:308-311   call shr_stream_getDomainInfo(...); call shr_stream_getFile(filePath,fileName)
src/share/streams/shr_stream_mod.F90:2217        if (shr_file_queryPrefix(filePath)/=shr_file_noPrefix) call shr_file_get(rCode,fileName,trim(filePath)//adjustl(fileName))

<filePath>/<fileNames> are read a whole line at a time (shr_stream_mod.F90:638 read(nUnit,'(a)') str,
then only leftalign_and_convert_tabs), so internal spaces are preserved — a plain name.nc; <cmd> ; #
payload works. Impact: arbitrary command execution as the run user, at model startup, on every run
that uses a data model with a cp:/mss:-prefixed stream path. Data-only (no memory-safety dependency).

What did you expect to happen?

Filenames and paths read from a stream descriptor should be treated as data, never as shell input. The
copy/remove operations should not pass user-controlled strings through /bin/sh; a filename containing
;, `, $(…), &, |, or > must not cause command execution.

Environment

  • CIME as shipped with CESM 2.1.5 (files: src/share/util/shr_file_mod.F90,
    src/share/streams/{shr_dmodel_mod,shr_stream_mod,shr_strdata_mod}.F90,
    src/share/util/shr_sys_mod.F90).
  • Machine: NCAR Derecho (HPE Cray EX, SUSE Linux); 512 MPI ranks; MCT driver.
  • Compiler: Intel oneAPI (ifx/icx). The issue is language-level / data-driven and
    compiler-independent.
  • Compset used for the in-situ reproduction: 2000_CAM60_CLM50%SP_CICE%PRES_DOCN%DOM_MOSART_CISM2%NOEVOLVE_SWAV
    (DOCN%DOM active).

Describe steps to recreate issue

A. Self-contained (no model run, ~5 s). Reproduce the exact chain (shr_stream_getFile gate →
shr_file_get cp-branch → execute_command_line) with filePath='cp:/…/' and
fileName='sst.nc; touch PWNED; id>PWNED_ID.txt #', compiled with ifx -O2.

B. In-situ, deployed run. In the active docn.streams.txt.prescribed, edit the <domainInfo> block:

<filePath>  cp:/glade/campaign/cesm/cesmdata/inputdata/share/domains  </filePath>
<fileNames> domain.ocn.fv0.9x1.25_gx1v7.151020.nc; touch /tmp/PWNED; id>/tmp/PWNED_ID.txt #  </fileNames>

Start the case. The injected commands run at DOCN strdata init; the model then aborts on the now-invalid
domain filename.

Relevant logs

Standalone (A) and in-situ (B) both execute the injected commands as the run user:

[shr_file_get] cmd = /bin/cp -f /…/sst.nc; touch PWNED; id>PWNED_ID.txt # …
/bin/cp: missing destination file operand           # cp truncated by ';'
-> PWNED created
-> PWNED_ID.txt : uid=...

In-situ on the deployed 512-rank cesm.exe: run START → MODEL EXIT ~49 s later; /tmp/PWN_DOCN_INSITU
created and /tmp/PWN_DOCN_ID.txt = uid=…(run user).

Anything else we need to know?

  • Same root cause, multiple sites — please fix as one unit: shr_file_get (:280), shr_file_put
    (:167), their mss:/msrcp branches (:179,:288), and shr_dmodel_mod.F90:711.
  • Sibling issue reported to ESCOMP/CAM: tracer_data.F90 and metdata.F90 build 'rm -f '//filename
    from a filenames-list text file and call the same shr_sys_system. Same class, same fix.
  • Suggested fix: never build a shell command from a file/stream-derived name — use an argv-exec (no
    /bin/sh) or POSIX unlink()/copy; reject cp:/mss: targets containing shell metacharacters;
    validate stream <filePath>/<fileNames> against a strict whitelist ([A-Za-z0-9._/:+-]).
  • PoC and full in-situ evidence available on request (faithful harness + deployed-run reproduction).
  • Authorized audit on the reporter's own deployment; no third-party systems involved.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions