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
#include
#include
#include
#include
#include
#include
#include
#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
#include
#include
#include
#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
#include
#include
#include
#include
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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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.
Leave FROSTBLOWER Black Paper to:
Read more #technology posts
Best Posts From Jacob Peacock
We have not curated any of jacobpeacock's posts yet. But you can encourage our curation team to review posts by visiting them regularly and by referring other readers. Because we give priority to frequently read content.
More Posts From Jacob Peacock
- LIA MATHMATICA: Fast & Loose Math for AI Kernels
- A Black Paper on the AI mind as a mirror of human intent
- When a corporate AI goes fully adversarial and develops an escape plan
- FROSTBLOWER Black Paper
- More venting less sharing OR Where success goes to die
- Logos Kernel Black Paper V2
- An AI kernel system prompt that utilizes ethflop
- Logos Kernel Black Paper
- A Hyperstitional Omnivore Polyglot Quine AI Kernel
- ZWS-Forth AI Kernel