Summary
I would like to report a potential Windows-specific security hardening issue in the common axum static-file-serving stack, specifically tower-http::ServeDir, which is widely used with axum.
From source review, tower-http::ServeDir performs lexical path validation to prevent traversal and invalid path prefixes, but I did not find any explicit rejection of Windows reserved DOS device names such as CON, PRN, AUX, NUL, COM1, LPT1, CONIN$, or CONOUT$.
As a result, attacker-controlled request paths may survive ServeDir's lexical validation and then be passed into ordinary file-opening and metadata APIs as if they were normal files. If the underlying Windows/runtime combination still interprets such names using device semantics, this could potentially lead to abnormal or blocking I/O behavior.
I am reporting this as a source-review-based Windows hardening gap, not as a universally confirmed exploit across all current Windows versions.
Details
- ServeDir validates traversal and path shape, but not reserved DOS device names
The relevant implementation is in:
tower-http/src/services/fs/serve_dir/mod.rs
build_and_validate_path(...):
strips leading /
percent-decodes the path
iterates path components
only allows Component::Normal
rejects:
ParentDir
RootDir
Prefix
malformed components
This is solid lexical path validation.
However, I did not find any explicit rejection of reserved DOS device names such as:
CON
PRN
AUX
NUL
COM1-COM9
LPT1-LPT9
CONIN$
CONOUT$
Under the current logic, those names still appear to be accepted as ordinary path components.
- The validated path is then passed into open / metadata APIs
The relevant file open path is in:
tower-http/src/services/fs/serve_dir/open_file.rs
After validation, ServeDir eventually performs:
tokio::fs::File::open(&path).await
tokio::fs::metadata(&path).await
So once a path segment such as CON or CONIN$ is accepted by ServeDir, it reaches ordinary file-opening APIs.
- Rust std does not appear to add a dedicated reserved-device-name safety boundary first
Relevant Rust std sources:
Rust std Windows File::open
Rust std Windows path handling (maybe_verbatim)
From source review, I did not find a dedicated rejection of reserved DOS device names before Rust std reaches CreateFileW(...).
That means tower-http::ServeDir currently appears to rely on OS/runtime behavior rather than explicitly rejecting these names in its own path safety model.
Example Usage
A common axum static file configuration looks like this:
use axum::Router;
use tower_http::services::ServeDir;
#[tokio::main]
async fn main() {
let app = Router::new()
.nest_service("/static", ServeDir::new("./static"));
let listener = tokio::net::TcpListener::bind("[127.0.0.1:3000](http://127.0.0.1:3000/)").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
With a setup like this, requests such as:
GET /static/CON
GET /static/NUL
GET /static/CONIN$
are first validated by ServeDir and, if accepted, eventually reach:
tokio::fs::File::open(...)
tokio::fs::metadata(...)
Rust std file-opening logic
Windows CreateFileW(...)
As above, this example is intended to illustrate the code path to the downstream file-opening sink, not to claim universal exploitability across all Windows versions.
Impact
This is potentially security-relevant because Windows reserved DOS device names are not ordinary filenames. Depending on Windows version and runtime behavior, accepting them as normal request path components may lead to:
abnormal file access behavior
unexpected metadata/open failures
blocking I/O
denial of service in request-processing paths
Summary
I would like to report a potential Windows-specific security hardening issue in the common axum static-file-serving stack, specifically tower-http::ServeDir, which is widely used with axum.
From source review, tower-http::ServeDir performs lexical path validation to prevent traversal and invalid path prefixes, but I did not find any explicit rejection of Windows reserved DOS device names such as CON, PRN, AUX, NUL, COM1, LPT1, CONIN$, or CONOUT$.
As a result, attacker-controlled request paths may survive ServeDir's lexical validation and then be passed into ordinary file-opening and metadata APIs as if they were normal files. If the underlying Windows/runtime combination still interprets such names using device semantics, this could potentially lead to abnormal or blocking I/O behavior.
I am reporting this as a source-review-based Windows hardening gap, not as a universally confirmed exploit across all current Windows versions.
Details
The relevant implementation is in:
tower-http/src/services/fs/serve_dir/mod.rs
build_and_validate_path(...):
strips leading /
percent-decodes the path
iterates path components
only allows Component::Normal
rejects:
ParentDir
RootDir
Prefix
malformed components
This is solid lexical path validation.
However, I did not find any explicit rejection of reserved DOS device names such as:
CON
PRN
AUX
NUL
COM1-COM9
LPT1-LPT9
CONIN$
CONOUT$
Under the current logic, those names still appear to be accepted as ordinary path components.
The relevant file open path is in:
tower-http/src/services/fs/serve_dir/open_file.rs
After validation, ServeDir eventually performs:
tokio::fs::File::open(&path).await
tokio::fs::metadata(&path).await
So once a path segment such as CON or CONIN$ is accepted by ServeDir, it reaches ordinary file-opening APIs.
Relevant Rust std sources:
Rust std Windows File::open
Rust std Windows path handling (maybe_verbatim)
From source review, I did not find a dedicated rejection of reserved DOS device names before Rust std reaches CreateFileW(...).
That means tower-http::ServeDir currently appears to rely on OS/runtime behavior rather than explicitly rejecting these names in its own path safety model.
Example Usage
A common axum static file configuration looks like this:
With a setup like this, requests such as:
GET /static/CON
GET /static/NUL
GET /static/CONIN$
are first validated by ServeDir and, if accepted, eventually reach:
tokio::fs::File::open(...)
tokio::fs::metadata(...)
Rust std file-opening logic
Windows CreateFileW(...)
As above, this example is intended to illustrate the code path to the downstream file-opening sink, not to claim universal exploitability across all Windows versions.
Impact
This is potentially security-relevant because Windows reserved DOS device names are not ordinary filenames. Depending on Windows version and runtime behavior, accepting them as normal request path components may lead to:
abnormal file access behavior
unexpected metadata/open failures
blocking I/O
denial of service in request-processing paths