Skip to content

Winstone WAR cleanup recurses through /proc/self/task/.../cwd outside configured webroot #26936

@mshahnavaz

Description

@mshahnavaz

Jenkins and plugins versions report

Environment
Jenkins: 2.555.3
OS: Linux - 5.4.17-2136.354.4.3.el8uek.x86_64
Java: 25.0.3 - Oracle Corporation (Java HotSpot(TM) 64-Bit Server VM)
---
ant:520.vd082ecfb_16a_9
antisamy-markup-formatter:173.v680e3a_b_69ff3
apache-httpcomponents-client-4-api:4.5.14-269.vfa_2321039a_83
apache-httpcomponents-client-5-api:5.6.1-195.v65ffe15189a_d
asm-api:9.10.1-216.va_9256d3b_844b_
authentication-tokens:1.144.v5ff4a_5ec5c33
authorize-project:531.vf8761dfd75d2
aws-java-sdk2-core:2.42.33-70.vd69c0763fa_60
badge:3.591.v9074c6c6f0b_9
blackduck-detect:11.0.0
bootstrap5-api:5.3.8-1038.vee76a_fe825ff
bouncycastle-api:2.30.1.84-291.v9f17b_21896e2
branch-api:2.1280.v0d4e5b_b_460ef
build-timeout:1.40
build-user-vars-plugin:214.va_eed2ed849ca_
built-on-column:1.5
caffeine-api:3.2.3-194.v31a_b_f7a_b_5a_81
checks-api:415.vf022234a_931d
cloudbees-folder:6.1100.ve9eed61d16c4
command-launcher:134.v025a_5fcf9dea_
commons-collections4-api:4.5.0-8.va_d5448ef9011
commons-compress-api:1.28.0-82.v4df6c060eb_66
commons-lang3-api:3.20.0-109.ve43756e2d2b_4
commons-text-api:1.15.0-218.va_61573470393
conditional-buildstep:1.5.0
configurationslicing:709.v4c76e613470f
copyartifact:795.ve8e151429b_27
credentials:1502.v5c95e620ddfe
credentials-binding:725.ve52b_2328a_fde
csp:2.58.v31cd65258cb_2
data-tables-api:2.3.8-1570.v1cb_1cd2a_0fb_c
deploy:1.17
description-setter:264.v1957f215dcd5
disable-job-button:1.3.vf55949267366
display-url-api:2.217.va_6b_de84cc74b_
docker-commons:472.vee120e23d3a_c
docker-workflow:634.vedc7242b_eda_7
downstream-buildview:69.v16da_b_2c36f6c
downstream-ext:73.vdda_16e6eb_0da
durable-task:671.v340ff7959010
echarts-api:6.0.0-1287.vfd24c22a_3d00
eddsa-api:0.3.0.1-29.v67e9a_1c969b_b_
email-ext:2038.v7b_8817a_499d9
emoji-symbols-api:17.0-57.v8d44b_9a_b_d5ea_
envinject:2.941.v351a_20c0a_3ca_
envinject-api:1.241.vdd714803b_403
external-monitor-job:223.vb_fddcf42c9b_3
font-awesome-api:7.2.0-990.vf220b_2a_496f9
fstrigger:1.02
git:5.10.1
git-client:6.6.0
git-server:137.ve0060b_432302
github:1.46.0.1
github-api:1.330-492.v3941a_032db_2a_
github-branch-source:1967.1969.v205fd594c821
gradle:2.19.1244.v1f9866817fec
gravatar:214.243.vd99f63e4175f
groovy:537.v741a_5a_f1b_581
groovy-postbuild:303.v5fe3da_6233f0
gson-api:2.14.0-201.v8eefe5515533
instance-identity:203.v15e81a_1b_7a_38
ionicons-api:94.vcc3065403257
jackson-annotations2-api:2.22-19.v10a_a_582ea_26e
jackson2-api:2.21.2-436.v29efdb_7418ff
jackson3-api:3.1.4-81.v804303fd947a_
jakarta-activation-api:2.1.4-1
jakarta-mail-api:2.1.5-1
jakarta-xml-bind-api:4.0.9-19.v2b_a_5b_44d9a_1c
javadoc:354.vee1a_660b_4990
javax-activation-api:1.2.0-8
javax-mail-api:1.6.2-11
jaxb:2.3.9-143.v5979df3304e6
jdk-tool:83.v417146707a_3d
jjwt-api:0.13.0-141.vd58b_a_9592b_6c
jnr-posix-api:3.1.22-204.v925e2b_09a_42c
jobConfigHistory:1356.ve360da_6c523a_
joda-time-api:2.14.2-193.v422b_efce56e0
jquery:1.12.4-3
jquery3-api:3.7.1-687.v68d468e40b_30
jsch:0.2.16-95.v3eecb_55fa_b_78
json-api:20260522-217.v0b_18b_8cd4672
json-path-api:3.0.0-218.vcd4dd1355de2
jsoup:1.22.2-95.vc5d00f1eb_42d
junit:1403.vd9d1413fd205
ldap:807.809.vd3a_4e5e4ec98
lockable-resources:1524.v2c727b_b_e56ef
log-parser:3.0.3
mailer:534.v1b_36f5864073
mapdb-api:1.0.9-44.va_1e1310c9118
mask-passwords:220.v95819055b_265
matrix-auth:3.2.10
matrix-project:870.v9db_fcfc2f45b_
maven-plugin:3.27
mina-sshd-api-common:2.17.1-187.v0341274c2905
mina-sshd-api-core:2.17.1-187.v0341274c2905
naginator:1.556.v14d723a_109a_c
nodelabelparameter:851.vd94e5048d321
okhttp-api:5.3.2-200.vedb_720a_cf1f8
oss-symbols-api:456.v46e634a_63b_99
pam-auth:1.12
parameterized-trigger:893.va_383a_9b_a_4c11
people-view:1.16.v774d3b_4b_3a_f3
pipeline-build-step:584.vdb_a_2cc3a_d07a_
pipeline-github-lib:65.v203688e7727e
pipeline-graph-analysis:254.v0f63a_a_447dca_
pipeline-groovy-lib:798.v5cc688825312
pipeline-input-step:551.vdff487c5998c
pipeline-milestone-step:152.v6e22b_8cfc66c
pipeline-model-api:2.2289.v6f731d0a_02da_
pipeline-model-definition:2.2289.v6f731d0a_02da_
pipeline-model-extensions:2.2289.v6f731d0a_02da_
pipeline-rest-api:2.41
pipeline-stage-step:345.va_96187909426
pipeline-stage-tags-metadata:2.2289.v6f731d0a_02da_
pipeline-stage-view:2.41
pipeline-utility-steps:3.810.va_7672d206740
plain-credentials:199.v9f8e1f741799
plugin-usage-plugin:418.v308c3c863d25
plugin-util-api:7.1341.v039f146993d9
postbuild-task:78.v24529f1f5cdb_
prism-api:1.30.0-741.v034eb_0b_0a_a_fa_
rebuild:338.va_0a_b_50e29397
resource-disposer:0.25
run-condition:276.v97298f3a_cd51
saml:4.600.v069e2e79eeb_f
scm-api:728.vc30dcf7a_0df5
script-security:1402.v94c9ce464861
shiningpanda:0.24
slack:795.v4b_9705b_e6d47
snakeyaml-api:2.5-149.v72471e9c6371
snakeyaml-engine-api:3.0.1-5.vd98ea_ff3b_92e
ssh-credentials:372.va_250881b_08cd
ssh-slaves:3.1097.v868116049892
sshd:3.384.vc89b_5e138cf9
structs:362.va_b_695ef4fdf9
subversion:1303.vcfd9679fb_c12
timestamper:1.30
token-macro:477.vd4f0dc3cb_cf1
trilead-api:2.284.v1974ea_324382
uno-choice:2.8.9
variant:70.va_d9f17f859e0
versioncolumn:400.v3c5c3004f31d
woodstox-core-api:7.1.1-1.v4d297985f397
workflow-aggregator:608.v67378e9d3db_1
workflow-api:1413.v2ff1a_5e720fa_
workflow-basic-steps:1098.v808b_fd7f8cf4
workflow-cps:4331.v9d06ed4658ff
workflow-durable-task-step:1479.v56e587f413a_7
workflow-job:1571.1580.v18e46842c125
workflow-multibranch:821.vc3b_4ea_780798
workflow-scm-step:466.va_d69e602552b_
workflow-step-api:724.v538c2362b_dfb_
workflow-support:1015.v785e5a_b_b_8b_22
ws-cleanup:0.49
xtrigger-api:1.2

What Operating System are you using (both controller, and any agents involved in the problem)?

Controller:
Linux x86_64
Kernel: 5.4.17-2136.354.4.3.el8uek.x86_64
Java: 25.0.3, Oracle Corporation, Java HotSpot(TM) 64-Bit Server VM

Agents:
No Jenkins agents were directly involved in the observed Winstone startup/webroot cleanup issue. The issue occurred during controller startup/WAR extraction before normal workload execution.

Reproduction steps

I do not yet have a clean minimal reproducer.

Observed sequence:

  • Run Jenkins from jenkins.war on Linux using Java 25.
  • Configure Jenkins to use an explicit Winstone webroot, for example:
    • --webroot=/path/to/jenkins-war
  • Start Jenkins as a non-root service user.
  • Jenkins home is separate from the Winstone webroot.
  • Upgrade Jenkins core from 2.555.2 to 2.555.3 using the Jenkins UI upgrade flow.
  • Use the UI-provided restart/upgrade flow after the upgrade.
  • During that UI-driven restart/startup path, Winstone began WAR extraction/webroot cleanup.
  • At that point the startup log showed winstone.HostConfiguration.deleteRecursive recursing through a /proc/self/task/<pid>/cwd/... path outside the configured webroot.
  • I then killed the Java process and started Jenkins using the system service manager (systemctl start jenkins / controlled service startup).
  • The service-managed startup appeared healthy and did not reproduce the same Winstone /proc/self/task/.../cwd recursion in the current startup log.
  • In a later controlled restart with auditd enabled, auditd confirmed Java attempted an unlink against a /proc/self/task/<pid>/cwd/... path. In the sampled event, the unlink failed with EACCES.
  • Successful Java delete events during the controlled restart were limited to the configured webroot and temp/lock files.

Relevant configuration:

  • Controller-only startup issue; no Jenkins agents were directly involved.
  • Explicit Winstone webroot was configured.
  • Jenkins home was separate from the Winstone webroot.
  • Jenkins was running as a non-root service user.
  • The configured webroot was owned by the Jenkins service user.
  • The configured webroot was not a symlink.
  • The configured webroot was not group/world writable.

Expected Results

Winstone should keep WAR extraction cleanup confined to the configured webroot.

Specifically:

  • Recursive cleanup should only delete files/directories under the configured --webroot path.
  • Cleanup should not recurse through /proc/self/task/<pid>/cwd or other process pseudo-filesystem paths.
  • Cleanup should not follow symlinks or other traversal paths that escape the configured webroot.
  • If Winstone detects an unsafe path or traversal outside the webroot, startup should fail safely or skip that path with a clear warning.

Actual Results

During the UI-driven Jenkins upgrade/restart flow, Winstone attempted recursive cleanup through a /proc/self/task/<pid>/cwd/... path outside the configured webroot.

The Jenkins startup log contained:

WARNING winstone.Logger#logInternal:
Failed to delete dirs /proc/self/task/<jenkins-pid>/cwd/proc/self/task/<jenkins-pid>/cwd/.../proc/<pid>/task/<pid>/net/psched

java.nio.file.AccessDeniedException:
proc/self/task/<jenkins-pid>/cwd/proc/self/task/<jenkins-pid>/cwd/.../proc/<pid>/task/<pid>/net/psched
    at java.base/sun.nio.fs.UnixException.translateToIOException(UnixException.java:90)
    at java.base/sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:106)
    at java.base/sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:111)
    at java.base/sun.nio.fs.UnixFileSystemProvider.implDelete(UnixFileSystemProvider.java:271)
    at java.base/sun.nio.fs.AbstractFileSystemProvider.deleteIfExists(AbstractFileSystemProvider.java:110)
    at java.base/java.nio.file.Files.deleteIfExists(Files.java:1087)
    at Jenkins Main ClassLoader//winstone.HostConfiguration.deleteRecursive(HostConfiguration.java:366)
    at Jenkins Main ClassLoader//winstone.HostConfiguration.deleteRecursive(HostConfiguration.java:362)
    ...
    at Jenkins Main ClassLoader//winstone.HostConfiguration.getWebRoot(HostConfiguration.java:282)
    at Jenkins Main ClassLoader//winstone.HostConfiguration.<init>(HostConfiguration.java:84)
    at Jenkins Main ClassLoader//winstone.HostGroup.initHost(HostGroup.java:59)
    at Jenkins Main ClassLoader//winstone.HostGroup.<init>(HostGroup.java:37)
    at Jenkins Main ClassLoader//winstone.Launcher.<init>(Launcher.java:171)
    at Jenkins Main ClassLoader//winstone.Launcher.main(Launcher.java:493)
    at executable.Main.main(Main.java:332)

After this occurred, I killed the Java process and restarted Jenkins through the system service manager. That service-managed startup completed successfully, and the current startup log did not contain the same deleteRecursive / /proc/self/task warning.

During a later controlled restart with auditd enabled, auditd captured Java attempting to unlink a /proc/self/task/<pid>/cwd/... path:

type=SYSCALL ... syscall=unlink success=no exit=EACCES(Permission denied) ... comm=java exe=/path/to/java key=delete_watch
type=PATH ... name=proc/self/task/<jenkins-pid>/cwd/proc/self/task/<jenkins-pid>/cwd/... nametype=DELETE

In that audited restart, the sampled /proc unlink failed with EACCES. Successful Java delete events were limited to the configured webroot and temp/lock files.

The concern is that Winstone recursive cleanup reached /proc/self/task/<pid>/cwd/... at all, despite an explicit --webroot being configured.

Anything else?

This may be related to Winstone WAR extraction cleanup when the existing extracted webroot is considered stale and is recursively deleted before re-extraction.

The relevant stack appears to be:

  • winstone.HostConfiguration.getWebRoot
  • winstone.HostConfiguration.deleteRecursive
  • java.nio.file.Files.deleteIfExists

The concern is not that the sampled /proc unlink succeeded. In the audited restart, it failed with EACCES. The concern is that recursive cleanup reached /proc/self/task/<pid>/cwd/... at all, even though an explicit --webroot was configured.

Local mitigations currently applied:

  • Use an explicit --webroot.
  • Ensure the webroot is owned by the Jenkins service user.
  • Ensure the webroot is not a symlink.
  • Ensure the webroot is not group/world writable.
  • Keep auditd delete rules enabled during controlled restarts.
  • Avoid UI-driven restart/upgrade flow where possible; prefer controlled service restart.
  • Preserve timestamped Jenkins startup logs for future analysis.

I initially considered whether this might have security impact because it involves recursive delete traversal outside the intended cleanup tree. I am filing this as a public bug report with internal hostnames, paths, and user details sanitized.

Are you interested in contributing a fix?

I am not currently familiar enough with Winstone internals to commit to contributing a fix, but I am willing to help validate a proposed fix or provide additional sanitized logs/audit output if needed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions