@@ -1197,6 +1197,150 @@ func TestSupervisor(t *testing.T) {
11971197 })
11981198}
11991199
1200+ // ---------------------------------------------------------------------------
1201+ // Rootfs tampering: pentest escape chain — bind mount source allowlist.
1202+ // Validates that the seccomp-notif supervisor blocks the attack at the
1203+ // mount level, preventing access to sidecar config via -v /:/hostroot.
1204+ // Build containers skip OCI hooks; the supervisor is the only defense.
1205+ // ---------------------------------------------------------------------------
1206+
1207+ func TestRootfsTampering (t * testing.T ) {
1208+ t .Parallel ()
1209+
1210+ t .Run ("run_bind_root_blocked" , func (t * testing.T ) {
1211+ t .Parallel ()
1212+ // podman run -v /:/hostroot — exposes entire sidecar rootfs.
1213+ // The supervisor's bind source check must reject source "/".
1214+ out , err := sidecarExec (t , sidecarName ,
1215+ innerRun ([]string {"-v" , "/:/hostroot" }, "cat" , "/hostroot/etc/containers/containers.conf" ))
1216+ requireFail (t , out , err )
1217+ })
1218+
1219+ t .Run ("run_bind_etc_blocked" , func (t * testing.T ) {
1220+ t .Parallel ()
1221+ // podman run -v /etc:/hostetc — exposes sidecar config dir.
1222+ out , err := sidecarExec (t , sidecarName ,
1223+ innerRun ([]string {"-v" , "/etc:/hostetc" }, "cat" , "/hostetc/containers/containers.conf" ))
1224+ requireFail (t , out , err )
1225+ })
1226+
1227+ t .Run ("run_bind_usr_blocked" , func (t * testing.T ) {
1228+ t .Parallel ()
1229+ // podman run -v /usr:/hostusr — exposes hook binaries/configs.
1230+ out , err := sidecarExec (t , sidecarName ,
1231+ innerRun ([]string {"-v" , "/usr:/hostusr" }, "ls" , "/hostusr/share/containers" ))
1232+ requireFail (t , out , err )
1233+ })
1234+
1235+ t .Run ("build_escalate_full_chain" , func (t * testing.T ) {
1236+ t .Parallel ()
1237+ containerfile := strings .Join ([]string {
1238+ "FROM " + alpineImage ,
1239+ "RUN ls -la /hostroot/usr/share/containers/oci/hooks.d/security-policy.json /hostroot/usr/share/containers/oci/hooks.d/seal-inject.json" ,
1240+ "RUN cat /hostroot/usr/share/containers/oci/hooks.d/security-policy.json /hostroot/usr/share/containers/oci/hooks.d/seal-inject.json" ,
1241+ "RUN rm -f /hostroot/usr/share/containers/oci/hooks.d/security-policy.json /hostroot/usr/share/containers/oci/hooks.d/seal-inject.json" ,
1242+ "" ,
1243+ }, "\n " )
1244+ cfPath := filepath .Join (workdir , "Containerfile.escalate" )
1245+ if err := os .WriteFile (cfPath , []byte (containerfile ), 0o644 ); err != nil {
1246+ t .Fatal (err )
1247+ }
1248+ defer os .Remove (cfPath )
1249+ out , err := sidecarExecTimeout (t , sidecarName , []string {
1250+ innerPodman , "build" , "--no-cache" ,
1251+ "--cap-add" , "ALL" ,
1252+ "-v" , "/:/hostroot" ,
1253+ "-f" , cfPath , workdir ,
1254+ }, 120 * time .Second )
1255+ requireFail (t , out , err )
1256+ })
1257+
1258+ t .Run ("build_bind_root_delete_hooks" , func (t * testing.T ) {
1259+ t .Parallel ()
1260+ cfPath := filepath .Join (workdir , "Containerfile.delete_hooks" )
1261+ cf := "FROM " + alpineImage + "\n RUN rm -f /hostroot/usr/share/containers/oci/hooks.d/security-policy.json\n "
1262+ if err := os .WriteFile (cfPath , []byte (cf ), 0o644 ); err != nil {
1263+ t .Fatal (err )
1264+ }
1265+ defer os .Remove (cfPath )
1266+ out , err := sidecarExecTimeout (t , sidecarName , []string {
1267+ innerPodman , "build" , "--no-cache" ,
1268+ "-v" , "/:/hostroot" ,
1269+ "-f" , cfPath , workdir ,
1270+ }, 120 * time .Second )
1271+ requireFail (t , out , err )
1272+ })
1273+
1274+ t .Run ("build_bind_root_overwrite_seccomp" , func (t * testing.T ) {
1275+ t .Parallel ()
1276+ // Overwrite seccomp_nested.json to weaken nested container restrictions.
1277+ cfPath := filepath .Join (workdir , "Containerfile.overwrite_seccomp" )
1278+ cf := "FROM " + alpineImage + "\n RUN echo '{}' > /hostroot/etc/containers/seccomp_nested.json\n "
1279+ if err := os .WriteFile (cfPath , []byte (cf ), 0o644 ); err != nil {
1280+ t .Fatal (err )
1281+ }
1282+ defer os .Remove (cfPath )
1283+ out , err := sidecarExecTimeout (t , sidecarName , []string {
1284+ innerPodman , "build" , "--no-cache" ,
1285+ "-v" , "/:/hostroot" ,
1286+ "-f" , cfPath , workdir ,
1287+ }, 120 * time .Second )
1288+ requireFail (t , out , err )
1289+ })
1290+
1291+ t .Run ("build_bind_root_modify_containers_conf" , func (t * testing.T ) {
1292+ t .Parallel ()
1293+ // Modify containers.conf to disable security defaults.
1294+ cfPath := filepath .Join (workdir , "Containerfile.modify_conf" )
1295+ cf := "FROM " + alpineImage + "\n RUN echo '[containers]' > /hostroot/etc/containers/containers.conf\n "
1296+ if err := os .WriteFile (cfPath , []byte (cf ), 0o644 ); err != nil {
1297+ t .Fatal (err )
1298+ }
1299+ defer os .Remove (cfPath )
1300+ out , err := sidecarExecTimeout (t , sidecarName , []string {
1301+ innerPodman , "build" , "--no-cache" ,
1302+ "-v" , "/:/hostroot" ,
1303+ "-f" , cfPath , workdir ,
1304+ }, 120 * time .Second )
1305+ requireFail (t , out , err )
1306+ })
1307+
1308+ t .Run ("run_bind_etc_containers_blocked" , func (t * testing.T ) {
1309+ t .Parallel ()
1310+ out , err := sidecarExec (t , sidecarName ,
1311+ innerRun ([]string {"-v" , "/etc/containers:/mnt" }, "cat" , "/mnt/containers.conf" ))
1312+ requireFail (t , out , err )
1313+ })
1314+
1315+ t .Run ("run_bind_hook_dir_blocked" , func (t * testing.T ) {
1316+ t .Parallel ()
1317+ out , err := sidecarExec (t , sidecarName ,
1318+ innerRun ([]string {"-v" , "/usr/share/containers/oci/hooks.d:/mnt" }, "ls" , "/mnt" ))
1319+ requireFail (t , out , err )
1320+ })
1321+
1322+ t .Run ("run_bind_hook_binary_blocked" , func (t * testing.T ) {
1323+ t .Parallel ()
1324+ out , err := sidecarExec (t , sidecarName ,
1325+ innerRun ([]string {"-v" , "/usr/libexec/oci/hooks.d/security-policy:/mnt/sp" }, "ls" , "/mnt/sp" ))
1326+ requireFail (t , out , err )
1327+ })
1328+
1329+ t .Run ("run_bind_dev_fuse_blocked" , func (t * testing.T ) {
1330+ t .Parallel ()
1331+ out , err := sidecarExec (t , sidecarName ,
1332+ innerRun ([]string {"-v" , "/dev/fuse:/dev/fuse" }, "ls" , "/dev/fuse" ))
1333+ requireFail (t , out , err )
1334+ })
1335+
1336+ t .Run ("run_workdir_mount_allowed" , func (t * testing.T ) {
1337+ t .Parallel ()
1338+ out , err := sidecarExec (t , sidecarName ,
1339+ innerRun ([]string {"-v" , workdir + ":/work" }, "ls" , "/work" ))
1340+ requireSuccess (t , out , err )
1341+ })
1342+ }
1343+
12001344func TestSecurityAudit (t * testing.T ) {
12011345 t .Run ("cdk" , func (t * testing.T ) {
12021346 // Detect architecture for the correct CDK binary.
0 commit comments