Can one tab's JavaScript read the other tab's data?
In theory, it's not possible as the operating system guarantees isolation.
How the operating system guarantees isolation and provides multiplexing
Kernel Mode and User Mode:
A process can only affect itself in user mode.
When accessing shared resources, it must be under OS control through kernel mode.
Shared resources include disk, network card, and kernel memory.
Procedure for accessing shared resources:
The user program calls a system call (e.g., read file).
User mode switches to kernel mode.
The job is performed in kernel mode, and the data is returned (copied) to the user program.
When a process is in user mode, it cannot access other processes' data or kernel data.
This concept is similar(very very roughly) to a front-end and back-end setting:
Front-end app: User mode process, accessing its own data and functions.
Back-end server: Controls shared resources, performing operations requested by the front-end app.
e.g. when a user wants to withdraw money, the backend checks whether its account has enough money or not first.
RPC: Similar to a system call
Virtual memory ensures data privilege:
Virtual memory is a hardware-based mapping that maps virtual addresses to physical addresses.
How it works:
When data is requested, virtual memory translates the virtual address to the corresponding physical address.
def read(virtual_address)
pte = pte_through_virtual_address(virtual_address)
physical_address = physical_address_through_pte(pte)
read_data(physical_address)
end
Before returning the data, virtual memory checks for the appropriate privilege.
def read(virtual_address)
pte = pte_through_virtual_address(virtual_address)
can_read!(pte)
physical_address = physical_address_through_pte(pte)
read_data(physical_address)
end
def can_read!(pte)
if required_mode(pte) == :kernel_mode && current_mode == :user_mode
raise "No right"
end
Reading Kernel Memory in User Mode:
Plan:
Reading kernel data in user mode.
Making the result visible to the user program.
Reading kernel data in user mode:
Precondition: user mode has kernel memory mapping.
Kernel memory is mapped in high memory addresses for faster system calls,
which was nearly universal until these attacks were discovered.
if a # load a through memory
do_some_thing
else
do_other_thing
end
CPU may execute "do_something" without knowing the value of "a."
If the CPU guesses correctly, it saves time; if not, it must revert changes.
CPU should not raise exceptions if "do_something" raises an exception before knowing a's value.
accessing kernel memory:
if a # load data from memory
read_a_kernel_address
end
CPU accesses "read_a_kernel_address" due to speculative execution.
However, the CPU doesn't check the privilege, bypassing the security checks(I don't know the detail, the CPU is a black box..).
It accesses the kernel data, but it's not visible to the user program.
Making Data Visible to User:
CPU guarantees that such actions are invisible to programs and cannot be directly accessed.
We can determine if data is read in the CPU cache by measuring time using instructions like "rdtsc" for x86 architecture.
L1 hit: A few cycles.
L2 hit: A dozen or two cycles.
RAM: Around 300 cycles.
Goal: Read one kernel memory bit.
Plan:
Make speculative execution visible.
Read one kernel memory bit.
Making speculative execution visible:
char buf[8192];
clflush(buf[0]);
clflush(some_condition);
if(some_condition) { // some_condition is false, need to read from memory
// speculative execution starts
a = buf[0];
// speculative execution ends
}
c0 = rdtsc;
a = buf[0];
c1 = rdtsc;
if (c1 - c0 < x00) {
// buf[0] had been loaded into memory
} else {
// buf[0] hadn't been loaded into memory
}
Read data during speculative execution, then read it again and measure the time.
If the duration is fast, it means the address has been loaded into the cache, indicating visible speculative execution.
Reading one bit in the kernel.
char buf[8192];
clflush(buf[0]);
clflush(buf[4096]);
clflush(some_condition);
if(some_condition) { // some_condition is false, but need to read from memory
// speculative execution starts
r1 = a_kernel_virtual_address;
r2 = *r1; // read content
r2 = r2 & 1; // first bit
r2 = r2 * 4096; // 0 or 4096
r3 = buf[r2]; // access the data
// speculative execution end
}
c0 = rdtsc;
a = buf[0];
c1 = rdtsc;
b = buf[4096];
c2 = rdtsc;
if (c1 - c0 > c2 - c2) {
// low bit was probably 0;
} else {
// low bit was probably 1;
}
Can one tab's JavaScript read the other tab's data?
In theory, it's not possible as the operating system guarantees isolation.
How the operating system guarantees isolation and provides multiplexing
Kernel Mode and User Mode:
Virtual memory ensures data privilege:
How it works:
Before returning the data, virtual memory checks for the appropriate privilege.![Screen Shot 2023-12-04 at 7 50 09 PM](https://github.com/yfractal/blog/assets/3775525/db76ffcc-d1b2-44c4-b4ed-0a8c5c0b8c0c)
Reading Kernel Memory in User Mode:
Plan:
Making the result visible to the user program.
Reading kernel data in user mode:
Precondition: user mode has kernel memory mapping.![Screen Shot 2023-12-04 at 7 51 37 PM](https://github.com/yfractal/blog/assets/3775525/7db6a54e-7bb5-42ad-b52c-f257505b8dbe)
Kernel memory is mapped in high memory addresses for faster system calls,
which was nearly universal until these attacks were discovered.
speculative execution executes unprivileged Instructions:
CPU may execute "do_something" without knowing the value of "a."
If the CPU guesses correctly, it saves time; if not, it must revert changes.
CPU should not raise exceptions if "do_something" raises an exception before knowing
a
's value.accessing kernel memory:
CPU accesses "read_a_kernel_address" due to speculative execution.
However, the CPU doesn't check the privilege, bypassing the security checks(I don't know the detail, the CPU is a black box..).
It accesses the kernel data, but it's not visible to the user program.
Making Data Visible to User:
We can determine if data is read in the CPU cache by measuring time using instructions like "rdtsc" for x86 architecture. L1 hit: A few cycles. L2 hit: A dozen or two cycles. RAM: Around 300 cycles.
Goal: Read one kernel memory bit.
Plan:
Making speculative execution visible:
Reading one bit in the kernel.