OS command injection: data-model stream paths/filenames reach `/bin/cp` and `rm` via `system()` (`shr_file_mod.F90`, `shr_dmodel_mod.F90`)
- Dominant language
- Fortran
- Stars
- 3
- Forks
- 27
- PR merge metrics
- No merged PRs in 30d
Description
As originally reported by @Champollion9012 here: https://github.com/ESMCI/cime/issues/5008
----
**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 ``/``
entry is executed as a command.
`src/share/util/shr_file_mod.F90` — `shr_file_get` (get, ~line 274/280), `shr_file_put` (put, ~line 164/167):
```fortran
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):
```fortran
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 entry
```
The data models (DOCN/DATM/DICE/DLND/DROF) read a stream text descriptor at init and, for any stream
whose `` 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))
```
``/`` 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; ; #`
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 `` block:
```xml
cp:/glade/campaign/cesm/cesmdata/inputdata/share/domains
domain.ocn.fv0.9x1.25_gx1v7.151020.nc; touch /tmp/PWNED; id>/tmp/PWNED_ID.txt #
```
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 ``/`` 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.
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.