firebase / firebase/firebase-android-sdk

[firebase-ml-modeldownloader] downloadUri from server response is not validated against expected Google/Firebase domains before being passed to DownloadManager

Open
#8,011 1 comment 0 reactions 1 assignee Claimed by @rlazo View on GitHub
api: mlkit
Dominant language
Java
Stars
2.6k
Forks
710
Avg merge
2d 9h
Merged PRs (30d)
31

Description

## Summary

The `firebase-ml-modeldownloader` SDK fetches model metadata from `firebaseml.googleapis.com` and receives a `downloadUri` field in the JSON response. This URI is passed **directly and without any validation** to `DownloadManager.enqueue()`, which then downloads the file and stores it in the app's private storage as a trusted ML model.

The SDK is designed to **exclusively** download models hosted on Firebase/Google Cloud Storage — there is no public API for developers to supply a custom download URL. Despite this, the code places **zero constraints on the `downloadUri` value** at the point of consumption, creating an unnecessary and avoidable risk surface.

---

## Affected Files

| File | Line | Action |
|------|------|--------|
| `firebase-ml-modeldownloader/src/main/java/com/google/firebase/ml/modeldownloader/internal/CustomModelDownloadService.java` | ~370 | `downloadUrl = reader.nextString()` — raw URL read from JSON, no validation |
| `firebase-ml-modeldownloader/src/main/java/com/google/firebase/ml/modeldownloader/internal/ModelFileDownloadService.java` | ~283 | `downloadManager.enqueue(new Request(Uri.parse(customModel.getDownloadUrl())))` — unvalidated URL passed to DownloadManager |

---

## Code Flow

```
Firebase ML backend response (JSON):
{ "downloadUri": "", "expireTime": "...", "sizeBytes": 1234 }
→ CustomModelDownloadService.readCustomModelResponse() [line ~370]
→ downloadUrl = reader.nextString() ← NO host/scheme check
→ CustomModel object stores the raw URL
→ ModelFileDownloadService.scheduleModelDownload() [line ~283]
→ new Request(Uri.parse(customModel.getDownloadUrl()))
→ downloadManager.enqueue(request)
→ Android DownloadManager fetches the URL
→ File written to app private storage as a trusted ML model
```

---

## Why This Is a Problem

The `downloadUri` in the server response is **always expected to be a Google Cloud Storage URL** (e.g., `https://firebasestorage.googleapis.com/...` or `https://storage.googleapis.com/...`). The SDK was never designed to accept model files from third-party hosts.

However, the code **does not enforce this expectation**. Any URL that reaches `DownloadManager.enqueue()` will be fetched and stored unconditionally.

Scenarios where a non-Google URL could reach this code path:

- **Corporate/enterprise network with MDM-managed CA**: extremely common in enterprise Android deployments where a trusted proxy CA is pushed via MDM, enabling transparent TLS inspection of all HTTPS traffic
- **Compromised or misconfigured Firebase ML backend** (supply-chain scenario)
- **Future regression** — a developer refactoring the URL construction logic could inadvertently introduce an externally-influenced URL without realising there is no downstream validation

> **Note:** This is not claimed as a trivially exploitable remote attack. It is a **defense-in-depth gap**: the SDK makes a strong implicit assumption about the URL it receives but never validates it, when doing so would be trivial and non-breaking.

---

## Impact

If a non-Google URL were to reach `DownloadManager.enqueue()` through any of the above paths:

1. **Arbitrary file download** — any binary can be written to the app's private `NoBackupFilesDir` under `com.google.firebase.ml.custom.models/`, labeled as a trusted model
2. **ML integrity violation** — even without code execution, a substituted model (e.g., a fraud-detection model that always outputs "not fraud") silently corrupts the app's ML-dependent behaviour
3. **Potential memory corruption** — the downloaded file is subsequently loaded by TensorFlow Lite. TFLite has a documented history of memory corruption vulnerabilities when parsing malformed model files (e.g., CVE-2022-23553), making this a plausible escalation path on devices with vulnerable TFLite versions

---

## Proposed Fix

### 1. Validate `downloadUri` against an allowlist of expected Firebase/Google domains

```java
// In CustomModelDownloadService.java — where downloadUrl is read from JSON
} else if (name.equals("downloadUri")) {
String rawUrl = reader.nextString();
Uri parsed = Uri.parse(rawUrl);
String host = parsed.getHost();
if (!"https".equals(parsed.getScheme()) ||
host == null ||
(!host.endsWith(".googleapis.com") &&
!host.endsWith(".firebasestorage.app") &&
!host.endsWith(".storage.googleapis.com"))) {
throw new IOException("downloadUri host is not an allowlisted Firebase/Google domain: " + host);
}
downloadUrl = rawUrl;
}
```

This fix has **zero impact on legitimate usage** — all real Firebase ML download URLs already come from `.googleapis.com` or `.firebasestorage.app`. No developer-facing API change is required.

### 2. (Recommended) Verify model file integrity before loading

The server response already includes a `modelHash` / ETag value. Verifying the SHA-256 of the downloaded file against this hash before passing it to TFLite would eliminate the file-substitution risk entirely, even in the absence of URL validation.

---

## References

- [`CustomModelDownloadService.java`](https://github.com/firebase/firebase-android-sdk/blob/main/firebase-ml-modeldownloader/src/main/java/com/google/firebase/ml/modeldownloader/internal/CustomModelDownloadService.java)
- [`ModelFileDownloadService.java`](https://github.com/firebase/firebase-android-sdk/blob/main/firebase-ml-modeldownloader/src/main/java/com/google/firebase/ml/modeldownloader/internal/ModelFileDownloadService.java)
- [CVE-2022-23553 — TFLite memory corruption via malformed model](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-23553)
- [Android DownloadManager docs](https://developer.android.com/reference/android/app/DownloadManager)

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.