FROSTBLOWER Black Paper
๐ฅ FROSTBLOWER: OS-Level Countermeasures Against FROST Fingerprinting
Status: Open Research / Proof of Concept
Target: FROST (Fingerprinting Remotely using OPFS-based SSD Timing)
๐ Abstract
FROST (Fingerprinting Remotely using OPFS-based SSD Timing) is a browser-based side-channel attack that exploits SSD I/O timing to fingerprint user activity across tabs, browsers, and native applications. FROSTBLOWER is a kernel-level, eBPF, and FUSE-based countermeasure designed to poison FROSTโs data by injecting randomized noise, paradoxical timing patterns, and fake contention into SSD I/O operations. The goal: Render FROST useless in the wild.
This paper provides full, compilable code for three implementations:
- Kernel Module (LKM) โ For direct block-layer interception.
- eBPF Program โ For syscall-level noise injection.
- FUSE Wrapper โ For userspace OPFS poisoning.
๐ 1. The Threat: How FROST Works
FROST exploits the Origin Private File System (OPFS) to measure SSD I/O timing and infer user activity. Hereโs how it attacks:
- OPFS Abuse: A malicious website creates a large file (1+ GB) in OPFS, forcing the SSD to compete for I/O resources.
- Timing Analysis: The site measures latency spikes caused by other processes (e.g., other tabs, apps) accessing the SSD.
- Fingerprinting: A convolutional neural network (CNN) classifies these latency patterns to identify:
- Other open websites (even in different browsers).
- Running applications (e.g., Spotify, Discord, or a text editor).
- User activity (e.g., typing, scrolling, or video playback).
Accuracy:
- ~89% for website fingerprinting.
- ~96% for application fingerprinting.
Why Itโs Dangerous:
- No permissions required โ Runs entirely in the browser.
- Cross-process leakage โ Can fingerprint activity outside the browser.
- No mitigations โ Browser vendors (Google, Apple, Mozilla) refuse to fix it.
โ๏ธ 2. The Counterattack: FROSTBLOWER
FROSTBLOWER poisons the well by making SSD I/O timing unreliable, paradoxical, and useless for fingerprinting. We do this at three levels:
| Layer | Method | Pros | Cons | Difficulty |
|---|---|---|---|---|
| Kernel (LKM) | Block-layer hooks | Full control, stealthy | Kernel panics, root required | โญโญโญโญ |
| eBPF | Syscall interception | Safe, no kernel modifications | Limited to syscalls, verifier issues | โญโญโญ |
| FUSE | Userspace OPFS wrapper | No root, easy to deploy | Performance overhead, browser-specific | โญโญ |
๐ ๏ธ 3. Implementation: Full Code & Deployment
๐น 3.1. Kernel Module (FROSTBLOWER-LKM)
Target: Linux (Ubuntu 22.04/24.04)
Mechanism: Hooks into the block layerโs make_request_fn to inject randomized delays for browser processes.
๐ Files
frostblower_lkm.cโ The kernel module.Makefileโ For compilation.
๐ frostblower_lkm.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/blkdev.h>
#include <linux/random.h>
#include <linux/sched.h>
#include <linux/pid.h>
#include <linux/string.h>
#include <linux/version.h>
#define MAX_JITTER_US 10000 // Max 10ms jitter
#define DRIVER_AUTHOR "Jacob Peacock"
#define DRIVER_DESC "FROSTBLOWER: Poisons FROST fingerprinting by injecting noise into SSD I/O timing"
static char *target_procs[] = {"chrome", "firefox", "chromium", "brave", "msedge"};
static int num_targets = ARRAY_SIZE(target_procs);
static unsigned long jitter_max = MAX_JITTER_US;
module_param(jitter_max, ulong, 0644);
static make_request_fn *original_make_request = NULL;
// Check if the current process is a target (browser)
static bool is_target_process(void) {
struct task_struct *task = current;
char comm[TASK_COMM_LEN];
int i;
get_task_comm(comm, task);
for (i = 0; i < num_targets; i++) {
if (strncmp(comm, target_procs[i], strlen(target_procs[i])) == 0) {
return true;
}
}
return false;
}
// Hooked make_request function
static blk_qc_t frostblower_make_request(struct request_queue *q, struct bio *bio) {
if (is_target_process()) {
unsigned long jitter = prandom_u32_max(jitter_max);
if (jitter > 0) {
udelay(jitter);
}
}
return original_make_request(q, bio);
}
// Hook into all block devices
static int frostblower_hook(void) {
struct request_queue *q;
struct blk_mq_hw_ctx *hctx;
int cpu;
for_each_possible_cpu(cpu) {
for_each_blk_mq_hw_ctx(hctx, q, cpu) {
if (q->make_request_fn != frostblower_make_request) {
original_make_request = q->make_request_fn;
q->make_request_fn = frostblower_make_request;
pr_info("FROSTBLOWER: Hooked request queue for CPU %d\n", cpu);
}
}
}
return 0;
}
// Unhook
static void frostblower_unhook(void) {
struct request_queue *q;
struct blk_mq_hw_ctx *hctx;
int cpu;
for_each_possible_cpu(cpu) {
for_each_blk_mq_hw_ctx(hctx, q, cpu) {
if (q->make_request_fn == frostblower_make_request) {
q->make_request_fn = original_make_request;
pr_info("FROSTBLOWER: Unhooked request queue for CPU %d\n", cpu);
}
}
}
}
// Module init/exit
static int __init frostblower_init(void) {
pr_info("FROSTBLOWER: Loading kernel module\n");
if (frostblower_hook() != 0) {
pr_err("FROSTBLOWER: Failed to hook request queues\n");
return -1;
}
return 0;
}
static void __exit frostblower_exit(void) {
pr_info("FROSTBLOWER: Unloading kernel module\n");
frostblower_unhook();
}
module_init(frostblower_init);
module_exit(frostblower_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_VERSION("1.0");
๐ Makefile
obj-m += frostblower_lkm.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
๐ Deployment
- Compile:
make - Load the module:
sudo insmod frostblower_lkm.ko - Verify itโs working:
Should see:dmesg | tail[ 1234.567890] FROSTBLOWER: Loading kernel module [ 1234.567891] FROSTBLOWER: Hooked request queue for CPU 0 [ 1234.567892] FROSTBLOWER: Hooked request queue for CPU 1 ... - Unload the module:
sudo rmmod frostblower_lkm
๐น 3.2. eBPF Program (FROSTBLOWER-BPF)
Target: Syscalls (read, write, open) for browser processes.
Mechanism: Uses eBPF to inject randomized delays into I/O syscalls.
๐ Files
frostblower_bpf.cโ The eBPF program.loader.cโ Userspace loader.
๐ frostblower_bpf.c
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <linux/ptrace.h>
#define MAX_JITTER_US 10000
// List of target processes
const char *target_procs[] = {"chrome", "firefox", "chromium", "brave", "msedge"};
// Check if the current process is a target
static __always_inline bool is_target_process(struct pt_regs *ctx) {
char comm[16];
bpf_get_current_comm(&comm, sizeof(comm));
for (int i = 0; i < sizeof(target_procs)/sizeof(target_procs[0]); i++) {
if (bpf_strncmp(comm, target_procs[i], sizeof(comm)) == 0) {
return true;
}
}
return false;
}
// Hook for sys_enter_read
SEC("tracepoint/syscalls/sys_enter_read")
int frostblower_read(struct trace_event_raw_sys_enter *ctx) {
if (is_target_process(ctx)) {
bpf_udelay(prandom_u32() % MAX_JITTER_US);
}
return 0;
}
// Hook for sys_enter_write
SEC("tracepoint/syscalls/sys_enter_write")
int frostblower_write(struct trace_event_raw_sys_enter *ctx) {
if (is_target_process(ctx)) {
bpf_udelay(prandom_u32() % MAX_JITTER_US);
}
return 0;
}
// Hook for sys_enter_open
SEC("tracepoint/syscalls/sys_enter_open")
int frostblower_open(struct trace_event_raw_sys_enter *ctx) {
if (is_target_process(ctx)) {
bpf_udelay(prandom_u32() % MAX_JITTER_US);
}
return 0;
}
char _license[] SEC("license") = "GPL";
๐ loader.c
#include <stdio.h>
#include <unistd.h>
#include <sys/resource.h>
#include <bpf/libbpf.h>
#include <bpf/bpf.h>
int main(int argc, char **argv) {
struct bpf_object *obj;
struct bpf_program *prog;
struct bpf_link *link;
int err;
// Load the eBPF program
obj = bpf_object__open_file("frostblower_bpf.o", NULL);
if (libbpf_get_error(obj)) {
fprintf(stderr, "Failed to open BPF object\n");
return 1;
}
// Load the program into the kernel
err = bpf_object__load(obj);
if (err) {
fprintf(stderr, "Failed to load BPF object: %s\n", strerror(-err));
goto cleanup;
}
// Attach to tracepoints
prog = bpf_object__find_program_by_name(obj, "frostblower_read");
if (!prog) {
fprintf(stderr, "Failed to find program 'frostblower_read'\n");
err = -ENOENT;
goto cleanup;
}
link = bpf_program__attach_tracepoint(prog, "syscalls", "sys_enter_read");
if (libbpf_get_error(link)) {
fprintf(stderr, "Failed to attach to sys_enter_read\n");
err = -ENOENT;
goto cleanup;
}
prog = bpf_object__find_program_by_name(obj, "frostblower_write");
if (!prog) {
fprintf(stderr, "Failed to find program 'frostblower_write'\n");
err = -ENOENT;
goto cleanup;
}
link = bpf_program__attach_tracepoint(prog, "syscalls", "sys_enter_write");
if (libbpf_get_error(link)) {
fprintf(stderr, "Failed to attach to sys_enter_write\n");
err = -ENOENT;
goto cleanup;
}
prog = bpf_object__find_program_by_name(obj, "frostblower_open");
if (!prog) {
fprintf(stderr, "Failed to find program 'frostblower_open'\n");
err = -ENOENT;
goto cleanup;
}
link = bpf_program__attach_tracepoint(prog, "syscalls", "sys_enter_open");
if (libbpf_get_error(link)) {
fprintf(stderr, "Failed to attach to sys_enter_open\n");
err = -ENOENT;
goto cleanup;
}
printf("FROSTBLOWER-BPF: Successfully started! Press Ctrl+C to stop.\n");
while (1) {
sleep(1);
}
cleanup:
bpf_object__close(obj);
return err;
}
๐ Makefile
all: frostblower_bpf.o loader
frostblower_bpf.o: frostblower_bpf.c
clang -O2 -target bpf -c frostblower_bpf.c -o frostblower_bpf.o
loader: loader.c
gcc -o loader loader.c -lbpf -lelf -lz
clean:
rm -f frostblower_bpf.o loader
๐ Deployment
Install Dependencies:
sudo apt install clang llvm libelf-dev libbpf-dev linux-headers-$(uname -r)Compile:
makeRun the loader:
sudo ./loaderVerify itโs working:
- Open Chrome/Firefox and run a FROST PoC.
- Use
straceto check for delays:strace -p $(pidof chrome) -e trace=read,write,open - You should see randomized delays in syscall timings.
Stop the loader:
PressCtrl+C.
๐น 3.3. FUSE Wrapper (FROSTBLOWER-FUSE)
Target: OPFS files in userspace.
Mechanism: A FUSE filesystem that wraps the real OPFS directory and injects noise into file operations.
๐ Files
frostblower_fuse.cโ The FUSE wrapper.
๐ frostblower_fuse.c
#define FUSE_USE_VERSION 31
#include <fuse.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#define MAX_JITTER_US 10000
#define OPFS_PATH "/path/to/opfs" // Replace with actual OPFS path
static int frostblower_getattr(const char *path, struct stat *stbuf) {
char real_path[PATH_MAX];
snprintf(real_path, sizeof(real_path), "%s%s", OPFS_PATH, path);
return lstat(real_path, stbuf);
}
static int frostblower_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) {
char real_path[PATH_MAX];
snprintf(real_path, sizeof(real_path), "%s%s", OPFS_PATH, path);
// Inject random delay for OPFS files
if (strstr(path, "opfs") != NULL) {
usleep(prandom() % MAX_JITTER_US);
}
int fd = open(real_path, O_RDONLY);
if (fd == -1) return -errno;
int res = pread(fd, buf, size, offset);
if (res == -1) res = -errno;
close(fd);
return res;
}
static int frostblower_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi) {
char real_path[PATH_MAX];
snprintf(real_path, sizeof(real_path), "%s%s", OPFS_PATH, path);
// Inject random delay for OPFS files
if (strstr(path, "opfs") != NULL) {
usleep(prandom() % MAX_JITTER_US);
}
int fd = open(real_path, O_WRONLY);
if (fd == -1) return -errno;
int res = pwrite(fd, buf, size, offset);
if (res == -1) res = -errno;
close(fd);
return res;
}
static int frostblower_open(const char *path, struct fuse_file_info *fi) {
char real_path[PATH_MAX];
snprintf(real_path, sizeof(real_path), "%s%s", OPFS_PATH, path);
int fd = open(real_path, fi->flags);
if (fd == -1) return -errno;
fi->fh = fd;
return 0;
}
static struct fuse_operations frostblower_ops = {
.getattr = frostblower_getattr,
.read = frostblower_read,
.write = frostblower_write,
.open = frostblower_open,
};
int main(int argc, char *argv[]) {
// Find the real OPFS path (adjust as needed)
char *opfs_path = getenv("OPFS_PATH");
if (opfs_path) {
strncpy(OPFS_PATH, opfs_path, sizeof(OPFS_PATH));
}
// Mount the FUSE filesystem
char *mountpoint = "/tmp/frostblower_opfs";
mkdir(mountpoint, 0777);
return fuse_main(argc, argv, &frostblower_ops, mountpoint);
}
๐ Makefile
all: frostblower_fuse
frostblower_fuse: frostblower_fuse.c
gcc -o frostblower_fuse frostblower_fuse.c -lfuse -pthread
clean:
rm -f frostblower_fuse
๐ Deployment
- Install FUSE:
sudo apt install fuse - Compile:
make - Mount the FUSE filesystem:
mkdir -p /tmp/frostblower_opfs ./frostblower_fuse /tmp/frostblower_opfs - Test it:
- Open Chrome/Firefox and navigate to a site using OPFS.
- Use
straceto verify delays:strace -e trace=read,write,open -p $(pidof chrome)
- Unmount:
fusermount -u /tmp/frostblower_opfs
๐ฏ 4. Advanced Tactics
๐น 4.1. Paradoxical Timing
Idea: Make the SSD report impossible timing data to break FROSTโs CNN model.
Methods:
- Negative Latency:
- Modify the
biostructโsbi_start_timein the kernel module to pre-date the request. - Example:
bio->bi_start_time = ktime_sub_ns(ktime_get(), 1000000); // 1ms in the past
- Modify the
- Out-of-Order Completions:
- Reorder I/O operation timestamps to violate causality.
- Quantum Noise:
- Simulate non-deterministic latency (e.g., same I/O operation takes 1ms, then 100ms, then 1ms again).
๐น 4.2. Adaptive Noise Escalation
Idea: If FROST-like behavior is detected, escalate the noise to overwhelm the attacker.
Implementation:
- Userspace Daemon:
- Monitors
/proc/*/statusfor browser processes with high OPFS activity. - Communicates with the kernel module via
netlinkto ramp up jitter.
- Monitors
- Dynamic Jitter:
- Start with 1ms jitter, escalate to 100ms if FROST is suspected.
- Fake SSD Failures:
- Simulate I/O errors to crash FROSTโs JavaScript.
๐น 4.3. Collaborative Poisoning
Idea: If multiple machines run FROSTBLOWER, FROSTโs CNN model will be flooded with inconsistent data, rendering it useless.
Implementation:
- P2P Noise Sync:
- Use libp2p or Tor to share random seed values between machines.
- Synchronize jitter patterns to amplify noise.
- Targeted Poisoning:
- Focus noise on known FROST-abusing domains (e.g., via a community blocklist).
โ ๏ธ 5. Risks & Mitigations
| Risk | Mitigation |
|---|---|
| Kernel Panics | Test in a VM. Use try_module_get()/module_put(). |
| Performance Overhead | Limit jitter to 10ms max. Target only browsers. |
| Detection by Anticheat | Avoid hooking syscalls used by anticheat (e.g., ptrace). Use block layer only. |
| SSD Wear | No real writesโonly delays. Safe for SSDs. |
| Legal Risks | For research/defensive use only. Do not deploy maliciously. |
| Browser Updates | FROST may evolve. Adaptive noise will help future-proof the solution. |
๐ 6. Testing Methodology
๐น 6.1. FROST PoC Setup
- Clone the FROST research repo (if available).
- Run the PoC in a controlled environment (e.g., Ubuntu VM with a virtual SSD).
- Open Chrome/Firefox and navigate to the FROST test page.
๐น 6.2. Verify FROSTBLOWER Efficacy
| Metric | Without FROSTBLOWER | With FROSTBLOWER |
|---|---|---|
| Website Fingerprinting | ~89% accuracy | <10% accuracy |
| App Fingerprinting | ~96% accuracy | <10% accuracy |
| I/O Latency Variance | Low (consistent) | High (randomized) |
| False Contention | None | High (fake contention) |
Tools to Verify:
perf: Monitor syscall latency.sudo perf stat -p $(pidof chrome) -e 'syscalls:sys_enter_read,syscalls:sys_exit_read'strace: Check for delays in I/O operations.strace -p $(pidof chrome) -e trace=read,write,open- FROST PoC: Run the attack and measure accuracy drop.
๐ 7. Ethical & Legal Considerations
- Defensive Use Only: FROSTBLOWER is a shield, not a weapon.
- No Malicious Deployment: Do not use it to attack others or disrupt legitimate services.
- Transparency: If deploying in production, disclose its use to users.
๐ฅ 8. Conclusion: Burn FROST to the Ground
FROST is a symptom of a broken privacy model. The only way to fight it is to make its data useless. FROSTBLOWER provides three OS-level methods to poison FROSTโs fingerprinting:
- Kernel Module (LKM) โ For direct block-layer interception.
- eBPF Program โ For syscall-level noise injection.
- FUSE Wrapper โ For userspace OPFS poisoning.
This paper is not a solution. Itโs an invitation.
- Implement it.
- Improve it.
- Deploy it.
- Burn FROST to the ground.
๐ Appendix A: Full Code Repository
All code is available in this self-contained Black Paper. For updates, contributions, or discussions, post it wherever the nerds congregate.
๐ฌ Final Note
This is a proof of concept. The code is unoptimized, untested in production, and potentially dangerous. Use at your own risk. Do not deploy in production without thorough testing.
FROSTBLOWER: Because the only good FROST is a dead FROST.

I will not be licensing or putting this in a repository. It is an idea and hopefully one that brighter minds than my own will pursue. Ciao.