Rust Support in SecMate: Two CVEs in RustFS

Rust is often praised for its memory safety guarantees, and rightfully so: in safe Rust, buffer overflows, use-after-free, and data races are eliminated at compile time. However, memory safety is not the same as security. Logic bugs, authentication flaws, and improper error handling can still leave Rust applications vulnerable.

To illustrate this, we ran SecMate’s automated analysis on RustFS [1], an S3-compatible object storage server written in Rust. We found two issues: an authentication bypass that lets attackers spoof their IP address to circumvent access controls (CVE-2026-21862 [2]), and a remote denial-of-service caused by unhandled deserialization errors (CVE-2025-69255 [3]).

Rust Support in SecMate

SecMate is an AI-powered static analysis tool specialized in the security of embedded software. We combine static analysis techniques with AI to find vulnerabilities that traditional tools miss: logic bugs, authentication flaws, and subtle design issues that require understanding how data flows through a system.

Rust is increasingly adopted in embedded and systems programming for its memory safety guarantees. Firmware, bootloaders, and security-critical components are being rewritten in Rust to eliminate entire classes of vulnerabilities. To support this trend, we recently added Rust to our analysis engine.

While RustFS is not an embedded application, it made an excellent first target: it is a security-sensitive system handling authentication, access control, and network protocols. This is the kind of complex logic where vulnerabilities hide regardless of the language.

What is RustFS?

RustFS is an S3-compatible object storage server written in Rust. Think of it as a self-hosted alternative to Amazon S3: you can store and retrieve objects using the same API that countless applications already support.

Key features include:

  • Full S3 API compatibility for drop-in replacement
  • IAM policies for fine-grained access control
  • gRPC interface for cluster administration and metrics
  • Support for erasure coding and distributed deployments

RustFS is designed for performance and reliability. It handles sensitive data and access control decisions, making security critical.

Vulnerability #1: IP-Based Authentication Bypass (CVE-2026-21862)

Location: rustfs/src/auth.rs:294 (get_condition_values)

The Bug

RustFS supports IAM policies that restrict access based on the client’s IP address. This is a common pattern: allow operations only from trusted networks, block everything else.

This feature exists because RustFS is often deployed behind a reverse proxy or load balancer. In that setup, every TCP connection appears to come from the proxy, not the real client. To preserve the original client address, proxies add forwarding headers like X-Forwarded-For, and applications use them for both access control and logging. That design only works if the proxy is trusted.

The problem is how RustFS determines the client’s IP. It reads forwarding headers like X-Forwarded-For or X-Real-IP and trusts them unconditionally:

1let remote_addr = header
2    .get("x-forwarded-for")
3    .and_then(|v| v.to_str().ok())
4    .and_then(|s| s.split(',').next())
5    .or_else(|| header.get("x-real-ip").and_then(|v| v.to_str().ok()))
6    .unwrap_or("127.0.0.1");
7
8args.insert("SourceIp".to_owned(), vec![get_source_ip_raw(header, remote_addr)]);

RustFS never verifies that the request actually came through a trusted proxy. Any client can set these headers directly.

The Attack

An attacker can bypass IP-based access controls by simply adding a forged header to their request:

Attacker(IP: 1.2.3.4)RustFSIAM PolicyHTTP RequestX-Forwarded-For: 10.0.0.5(forged header)Check: is 10.0.0.5 allowed?Policy allows 10.0.0.5/32Allowed200 OK - Access Granted

Here is what happens step by step:

  1. An IAM policy allows s3:ListBucket only from IP 10.0.0.5/32 (an internal server)
  2. The attacker connects from their external IP 1.2.3.4
  3. They add the header X-Forwarded-For: 10.0.0.5 to their request
  4. RustFS reads the header and believes the client IP is 10.0.0.5
  5. The IAM policy check passes, and the attacker gets access

Impact

This vulnerability bypasses IP-based access controls when headers are attacker-controlled, such as when clients connect directly to RustFS or through an untrusted proxy. If a trusted proxy controls or strips these headers before forwarding, the risk is reduced. However, in deployments where clients can reach RustFS directly, aws:SourceIp conditions become bypassable. An attacker can:

  • Access buckets restricted to internal networks
  • Bypass IP allowlists meant for trusted services
  • Circumvent geographic restrictions
  • Spoof audit trails and security monitoring by forging client IPs
  • In the version we analyzed, missing headers fell back to 127.0.0.1, so policies allowing localhost could unintentionally allow remote requests

Fix Status

This vulnerability has been fixed. RustFS now:

  • Provides an environment variable (_RUSTFS_API_XFF_HEADER) to disable forwarding header processing entirely
  • Falls back to the actual TCP connection’s peer address when headers are missing or disabled
  • Operators deploying RustFS without a trusted proxy should set _RUSTFS_API_XFF_HEADER=off

Vulnerability #2: Remote DoS via gRPC Panic (CVE-2025-69255)

Location: rustfs/src/storage/tonic_service.rs:1768-1785 (get_metrics)

The Bug

RustFS exposes a gRPC interface for cluster operations, including a GetMetrics endpoint. The handler deserializes client-supplied bytes using Deserialize::deserialize() and calls .unwrap() on the result:

1// In NodeService::get_metrics handler
2let t = Deserialize::deserialize(&mut buf_t).unwrap();  // Panics on bad input!
3let o = Deserialize::deserialize(&mut buf_o).unwrap();  // Same problem here

In Rust, .unwrap() is convenient for cases where failure is truly unexpected. But here, the input comes from the network. An attacker controls what bytes get deserialized. If they send malformed data, deserialization fails, and .unwrap() triggers a panic, aborting the request.

RustFS does wrap request handling in a panic-catching layer, so a single panic usually returns an error instead of killing the whole process. That limits the blast radius, but panics are still expensive to handle.

The Attack

The attack requires sending a malformed gRPC request. When a client sends an empty or invalid metric_type field, the server attempts to deserialize it, fails, and the .unwrap() call causes a panic:

When the malformed request arrives, the server logs a panic:

thread 'rustfs-worker' panicked at rustfs/src/storage/tonic_service.rs:1778:66:
called `Result::unwrap()` on an `Err` value: InvalidMarkerRead(Error {
    kind: UnexpectedEof,
    message: "failed to fill whole buffer"
})

Impact

This was a remote denial-of-service risk. The gRPC endpoint uses HMAC signature authentication via x-rustfs-signature and x-rustfs-timestamp headers. A single bad request only affected that one request, but repeated exploitation could cause a real denial-of-service. An attacker with valid credentials could send many malformed requests (or parallelize them across connections) to repeatedly trigger panics, which:

  • Consumed CPU time in panic handling and stack unwinding
  • Flooded logs with panic traces and slowed down observability pipelines
  • Starved gRPC worker threads so GetMetrics became effectively unavailable
  • Could slow other gRPC handlers that share the same runtime under high load

The fix was straightforward: replace .unwrap() with proper error handling that returns an error response instead of panicking:

 1// Fixed version
 2let t: MetricType = match Deserialize::deserialize(&mut buf_t) {
 3    Ok(t) => t,
 4    Err(err) => {
 5        return Ok(Response::new(GetMetricsResponse {
 6            success: false,
 7            error_info: Some(format!("Invalid metric_type: {err}")),
 8            ..Default::default()
 9        }));
10    }
11};

Fix Status

This vulnerability has been fixed in current RustFS versions. The .unwrap() calls are gone, and deserialization errors now return a response with success=false and an error string instead of panicking. The fix includes tests to ensure invalid metric_type and opts values do not cause panics.

The Rust Security Reality

Both vulnerabilities highlight an important point: Rust’s safety guarantees have limits.

In safe Rust, entire classes of memory-safety bugs are eliminated at compile time. You will not find buffer overflows, use-after-free, or data races in safe Rust code. This is a huge win for security.

But Rust does not prevent:

  • Logic bugs: Trusting user-controlled headers for authentication decisions
  • Error handling mistakes: Using .unwrap() on fallible operations with untrusted input
  • Design flaws: Exposing sensitive endpoints without proper authentication
  • Cryptographic errors: Using weak algorithms or improper key management
Key Insight

Memory safety is necessary but not sufficient for security. Rust eliminates one category of vulnerabilities, but logic bugs and design flaws require the same careful analysis as in any other language.

These findings demonstrate why AI-assisted code analysis remains valuable even for memory-safe languages. SecMate identified these vulnerabilities by analyzing data flow and reasoning about how untrusted input affects security decisions, something that applies regardless of the language.

Disclosure Timeline

Dec 8, 2025
SecMate reported both vulnerabilities to RustFS maintainers
Dec 28, 2025
Maintainers acknowledged and confirmed the issues
Dec 30, 2025
Patches merged and released

We thank the RustFS maintainers for their responsiveness in addressing these issues. Coordinated disclosure helps keep the ecosystem secure.

Want to Know More?

These two CVEs are the first vulnerabilities we are publicly disclosing in a Rust codebase, showcasing SecMate’s expanded language support. As Rust adoption grows in systems programming and embedded development, we expect to find more issues like these: logical flaws that memory safety alone cannot prevent.

Interested in our vulnerability research? Browse our public disclosures to see what else we have found. If you want SecMate to analyze your codebase, whether it is Rust, C, C++, or another language, get in touch.

References

  • [1] RustFS. “RustFS - High Performance Object Storage” GitHub. Repository | Documentation

  • [2] GitHub. “GHSA-fc6g-2gcp-2qrq - SourceIp bypass via spoofed X-Forwarded-For/Real-IP headers” GitHub Security Advisory. Advisory

  • [3] GitHub. “GHSA-gw2x-q739-qhcr - RustFS gRPC GetMetrics deserialization panic enables remote DoS” GitHub Security Advisory. Advisory


The SecMate Team