Speculative Execution — গতির পেছনে লুকানো নিরাপত্তা ফাঁক
Speculative Execution and Its Security Implications
Misprediction হলে CPU speculative কাজ 'যেন কখনো ঘটেইনি' এভাবে ফেলে দেয় — কিন্তু শুধু register আর memory-র জন্য। Cache-এ যে ছাপ পড়ে যায় তা থেকেই যায়, আর সেটাই Spectre ও Meltdown-এর ভিত্তি।
আগে এটা বুঝি
আগের লেসনে (computer-architecture/branch-prediction) আমরা দেখেছি
branch-এর ফলাফল না জেনেই CPU-কে fetch চালিয়ে যেতে হয় — তাই এটা
একটা 2-bit saturating counter দিয়ে শিক্ষিত অনুমান করে, আর ভুল হলে
speculatively-করা সব কাজ বাতিল করে সঠিক path থেকে আবার শুরু করে।
সেই লেসনের misconception অংশেই একটা লাইন লেখা ছিল — “misprediction
শুধু performance সমস্যা, correctness সমস্যা না, কারণ reorder buffer
নিশ্চিত করে শুধু সঠিক কাজই architectural state-এ commit হয়”।
এই লেসনের পুরো কাজ হলো সেই একটা লাইনকে প্রশ্ন করা। “Speculatively কাজ চালিয়ে যাওয়া” মানে ঠিক কী? আর “ভুল হলে বাতিল” মানে কি সত্যিই সবকিছু বাতিল হয়, নাকি শুধু কিছু অংশ? ২০১৮ সালে Spectre আর Meltdown নামের দুটো আবিষ্কার পুরো CPU industry-কে দেখিয়ে দিয়েছিল — উত্তরটা দ্বিতীয়টা। আর সেই ফাঁকটাই এই লেসনের মূল বিষয়।
মূল ধারণা
“Speculatively চালিয়ে যাওয়া” মানে আসলে কী
computer-architecture/register-renaming-reorder-buffer লেসনে
দেখা হয়েছে (এবং এই module-এর আগের লেসনগুলোতে বিস্তারিত) — একটা
আধুনিক out-of-order CPU-তে instruction fetch, decode, execute, আর
commit/retire সম্পূর্ণ আলাদা ধাপ। Branch predictor একটা
prediction দেওয়ার সাথে সাথেই CPU থেমে থাকে না — সে predicted
path-এর instruction fetch করে, decode করে, আর সবচেয়ে গুরুত্বপূর্ণ:
execute করে। এটা কোনো “as if” বা ধারণাগত জিনিস না — সত্যিকারের
ALU cycle খরচ হয়, সত্যিকারের load/store unit ব্যবহার হয়, আর load
instruction হলে সত্যিকারের memory/cache access হয়।
পার্থক্যটা কোথায় তাহলে? পার্থক্য হলো commit-এ। Execute হওয়া result সরাসরি architectural register file-এ বা memory-তে লেখা হয় না — সেটা Reorder Buffer (ROB)-এ একটা “pending” entry হিসেবে বসে থাকে, physical (renamed) register-এ। শুধু যখন branch resolve হয় (condition আসলে সত্যি কি মিথ্যা জানা যায়) আর predictor সঠিক প্রমাণিত হয়, তখনই ROB সেই entry-গুলোকে in-order commit করে — architectural state-এ স্থায়ীভাবে প্রতিফলিত করে।
Branch predict (Taken)
│
▼
┌───────────────────┐
│ Speculatively │ ← real ALU, real load/store unit,
│ fetch/decode/ │ real cache access — সবকিছু
│ execute করা হয় │ physically ঘটে
└─────────┬──────────┘
│ ফলাফল যায়
▼
┌───────────────────┐
│ ROB entry │ ← "pending", এখনো architectural
│ (physical reg) │ state না
└─────────┬──────────┘
│
branch resolve হয়
│
┌──────┴──────┐
▼ ▼
সঠিক ছিল ভুল ছিল
│ │
▼ ▼
commit flush (ROB entry ফেলে দেওয়া হয়,
(architectural architectural state কখনো ছোঁয়নি)
state আপডেট)এইখানেই এই লেসনের কেন্দ্রীয় প্রশ্ন লুকিয়ে। “Flush” মানে ROB entry-টা মুছে ফেলা, ঠিক আছে — কিন্তু সেই speculative instruction-টা execute হওয়ার সময় যদি cache-এ কিছু touch করে থাকে (একটা load instruction যদি একটা নতুন cache line memory থেকে এনে থাকে), সেই cache line কি ROB flush-এর সাথে সাথে “আন-লোড” হয়ে যায়? উত্তর: না। Cache একটা সম্পূর্ণ আলাদা hardware structure, ROB-এর part না, আর তার কোনো “undo” mechanism নেই। একবার একটা লাইন cache-এ ঢুকলে, সেটা সেখানেই থাকে (যতক্ষণ না অন্য কিছু eviction ঘটায়) — misprediction হোক বা না হোক।
দুই ধরনের state — একটা সংজ্ঞা যা এই পুরো লেসনের ভিত্তি
Architectural state — ISA যা define করে, যা software দেখতে পারে: general-purpose register, flags, program counter, আর memory-র বিষয়বস্তু। CPU vendor বদলালেও (Intel বনাম AMD, দুটোই x86-64) একই প্রোগ্রামের architectural state একই আচরণ করার কথা — এটাই ISA-র প্রতিশ্রুতি।
Microarchitectural state — কীভাবে সেই architectural আচরণ বাস্তবায়িত হয়, তার implementation-নির্দিষ্ট byproduct: cache-এর বিষয়বস্তু ও tag, TLB entry, branch predictor-এর history table, load/store buffer, এমনকি power/temperature-এর মতো পরোক্ষ সংকেতও। ISA কখনোই promise করে না যে microarchitectural state কেমন হবে — এটা সম্পূর্ণভাবে “ভেতরের ব্যাপার”, দুইটা ভিন্ন CPU model-এ সম্পূর্ণ ভিন্ন হতে পারে একই প্রোগ্রামের জন্য।
সেতুটা — cache state থেকে measurable signal-এ
তাহলে প্রশ্ন: cache-এ একটা লাইন লোড হয়ে গেলে সেটাকে কীভাবে
“পড়া” যায়, যদি architectural state-এ তার কোনো প্রতিফলন নেই? উত্তর
হলো — সরাসরি পড়া যায় না, কিন্তু পরোক্ষভাবে সময় মেপে অনুমান করা
যায়। এখানেই mathematics/asymptotic-notation লেসনের সেই
cache-locality experiment সরাসরি ফিরে আসে, কিন্তু সম্পূর্ণ নতুন
উদ্দেশ্যে।
সেই লেসনে আমরা দেখেছিলাম L1 cache hit ~4 cycle, DRAM miss ~200 cycle — প্রায় ৫০ গুণ পার্থক্য। ওই লেসনে এটা ব্যবহার হয়েছিল algorithm পারফরম্যান্স বোঝার জন্য (কেন array linked-list-এর চেয়ে দ্রুত)। এই লেসনে ঠিক একই timing gap-কে “অস্ত্র” বানানো হয় — যদি আমি জানতে পারি কোন cache line দ্রুত access হচ্ছে (hit) আর কোনটা ধীর (miss), আমি সেই তথ্য থেকে কোন লাইনটা আগে touch হয়েছিল তা recover করতে পারি। এটাই cache-timing side channel।
ভেতরে কী ঘটছে
Cache-timing side channel — নির্দিষ্ট mechanism
ধরুন attacker একটা probe array বানায় — ২৫৬টা ভাগ, প্রতিটা ভাগ একটা পুরো page (৪০৯৬ byte) দূরত্বে (একটা মাত্র cache line না রেখে বড় দূরত্ব রাখা হয় — কাছাকাছি রাখলে hardware prefetcher নিজে থেকেই পাশের লাইন এনে ফেলতে পারে, পরের লেসনে prefetcher বিস্তারিত দেখব, যা এই timing measurement-কে বিভ্রান্ত করে দিত)।
probe[0] → offset 0
probe[1] → offset 4096
probe[2] → offset 8192
...
probe[255] → offset 1,044,480ধাপ ১ — Flush। আক্রমণের আগে attacker নিশ্চিত করে পুরো probe
array cache-এর বাইরে আছে (হয় clflush-এর মতো instruction দিয়ে,
বা এত বড় অন্য কিছু access করে cache-কে “ধুয়ে” ফেলে)।
ধাপ ২ — Secret-নির্ভর touch। কোনোভাবে (Spectre বা Meltdown-এর
mechanism দিয়ে, নিচে দেখুন) speculative execution একটা secret byte
S (0-255-এর মধ্যে একটা মান) পড়ে, আর সেটা ব্যবহার করে
probe[S]-কে access করে — মানে ঠিক S-নম্বর ভাগের একটা cache line
memory থেকে load হয়ে যায়।
ধাপ ৩ — Misprediction/fault recognize, flush। CPU realize করে
কিছু একটা ভুল ছিল (branch mispredict, বা permission fault) —
architectural state clean থাকে, S-এর কোনো trace register বা
memory-তে নেই।
ধাপ ৪ — Reload আর timing। Attacker (এখন normal, non-speculative কোডে) ২৫৬টা probe entry-র প্রতিটাতে একবার করে access করে, প্রতিটার সময় মাপে:
| Probe index | Access time (cycle) | ব্যাখ্যা |
|---|---|---|
probe[0] | ~২১০ | cache-এ নেই — DRAM miss |
probe[1] | ~১৯৫ | cache-এ নেই |
| ⋮ | ⋮ | ⋮ |
probe[142] | ~৪ | cache-এ আছে — এটাই S! |
| ⋮ | ⋮ | ⋮ |
probe[255] | ~২০৮ | cache-এ নেই |
যেটা দ্রুত (hit), তার index-ই secret byte-এর মান। একটা বাইট বের করতে ২৫৬টা timing measurement লাগে (nanosecond-স্কেলে, তাই বাস্তবে মিলিসেকেন্ডেরও কম), আর পুরো একটা memory region byte-by-byte এভাবে “পড়ে” ফেলা সম্ভব — যদিও attacker কখনো architecturally সেই memory সরাসরি পড়েনি।
Spectre — branch predictor-কে বিভ্রান্ত করা
Spectre variant 1 (bounds check bypass, CVE-2017-5753) ঠিক এই ধরনের কোডকে লক্ষ্য করে:
if (x \< array1_size) {
y = array2[array1[x] * 4096];
}এটা সম্পূর্ণ স্বাভাবিক, নিরাপদ দেখতে কোড — একটা bounds check, তারপর নিরাপদ array access। কিন্তু speculative execution-এর চোখে এতে একটা ফাঁক আছে:
-
Train। Attacker বহুবার এই কোডটাকে বৈধ, in-bounds
xদিয়ে কল করে (যেমনx = 5, array1_size-এর মধ্যেই)। প্রতিবার bounds check সত্যি হয়, branch taken হয় — branch predictor “এই branch সবসময় taken” শিখে ফেলে (আগের লেসনের 2-bit saturating counter এখন Strongly Taken)। -
Exploit। এবার attacker একটা out-of-bounds
xদিয়ে কল করে — এমন একটা মান যাarray1 + xকে attacker যা পড়তে চায় (একটা secret byte, হয়তো একই process-এর অন্য অংশে, বা kernel-এর কাছাকাছি কিছু) সেই ঠিকানার দিকে নিয়ে যায়। -
Speculative misuse। যেহেতু predictor অত্যন্ত আত্মবিশ্বাসী (বহুবার taken দেখেছে), CPU বাস্তব comparison শেষ হওয়ার আগেই speculatively ধরে নেয় bounds check পাস করেছে, আর
array1[x]পড়া শুরু করে দেয় — এই read-টাই out-of-bounds, secret byteSনিয়ে আসে। তারপরarray2[S * 4096]access হয় speculatively — ঠিক উপরের probe mechanism-এর ধাপ ২। -
Resolve, flush, leak। কিছু cycle পরে actual comparison শেষ হয় —
x \< array1_sizeমিথ্যা প্রমাণিত হয়, misprediction ধরা পড়ে, ROB flush করে।y-এর মধ্যে ভুল ফলাফল কখনো architecturally দেখা যায় না। কিন্তুarray2-র একটা নির্দিষ্ট cache line ততক্ষণে লোড হয়ে গেছে — উপরের timing table দিয়ে সেটা measurable।
Spectre variant 2 (branch target injection, CVE-2017-5715) একই নীতির আরেকটা রূপ, কিন্তু conditional branch-এর বদলে indirect branch (যেমন C++ virtual function call, function pointer, বা switch-statement jump table) লক্ষ্য করে। CPU indirect branch-এর target আগে থেকে জানতে predictor ব্যবহার করে (কোন ঠিকানায় লাফ দিতে হবে সেটাও এক ধরনের prediction)। Attacker যদি সেই predictor-এর history-কে ইচ্ছাকৃতভাবে “poison” করতে পারে (নিজের কোড দিয়ে, একই predictor hardware-এ যেটা ভিন্ন privilege level বা ভিন্ন process-এও ব্যবহৃত হতে পারে), তাহলে victim code-এর একটা indirect branch speculatively attacker-এর বেছে নেওয়া একটা “gadget” ঠিকানায় লাফ দিতে পারে — সেই gadget-টা victim-এর নিজের memory-র মধ্যেই কোনো বিদ্যমান কোড-খণ্ড, যেটা speculatively execute হলে ঠিক Spectre v1-এর মতো একটা secret read করে cache-এ ছাপ ফেলে দেয়। এটাই সবচেয়ে বিপজ্জনক রূপ কারণ এটা process/privilege boundary পার হয়ে কাজ করতে পারে — যে কারণে retpoline (নিচে দেখুন) নির্দিষ্টভাবে এই variant-কে লক্ষ্য করে তৈরি হয়েছে।
Meltdown — সম্পূর্ণ ভিন্ন একটা mechanism
এখানে একটা গুরুত্বপূর্ণ স্পষ্টীকরণ দরকার — Spectre আর Meltdown একই বাগ না, দুটো সম্পূর্ণ ভিন্ন mechanism, যদিও দুটোই একই “speculative execution + cache side channel” পরিবারের অংশ, আর একই দিনে (জানুয়ারি ২০১৮) প্রকাশ পেয়েছিল।
Meltdown (CVE-2017-5754) branch predictor-কে বিভ্রান্ত করে না। বরং এটা একটা সম্পূর্ণ ভিন্ন race-এর সুযোগ নেয়: out-of-order execution-এ একটা load-এর ফলাফল dependent instruction-এ ব্যবহৃত হওয়া, বনাম সেই load-এর permission check সম্পূর্ণ হওয়া — এই দুটোর মধ্যে timing gap।
// user-mode কোড, kernel_addr একটা privileged (kernel-only) ঠিকানা
char secret = *(char*)kernel_addr; // এই read-টা eventually fault করবে
size_t idx = secret * 4096;
char touch = probe[idx]; // কিন্তু এই instruction-টা তার আগেই
// speculatively চলে যায়!প্রভাবিত CPU-তে (মূলত ২০১৯-এর আগের বেশিরভাগ Intel x86, কিছু ARM Cortex-A75) যা ঘটে:
*(char*)kernel_addrinstruction issue হয়। Load/store unit memory থেকে ডেটা আনা শুরু করে দেয়, permission check (এই code privilege level-এ এই ঠিকানা পড়ার অধিকার আছে কি না) এখনো সম্পূর্ণ হয়নি — এই দুটো ধাপ সমান্তরালে চলে, একটা পরেরটার জন্য অপেক্ষা করে না।- Data রেজিস্টারে (renamed physical register) চলে আসে, আর
dependent instruction-গুলো (পরের
idxগণনা,probe[idx]access) সেই মান ব্যবহার করে speculatively execute হয়ে যায় — ঠিক Spectre-এর মতোই একটা cache line touch হয়ে যায়, secret byte-এর মান encode করে। - কিছু cycle পরে permission check সম্পূর্ণ হয়, violation ধরা পড়ে
— CPU একটা fault (যেমন general protection fault) তোলে। এই
instruction আর তার পরের সবকিছু কখনো retire/commit হয় না —
architecturally,
secret-এর মান কখনো user-mode কোডে দেখা যায়নি, প্রোগ্রাম crash করে (বা attacker সেই fault-কে catch/suppress করে রাখে, যেমন signal handler বা transactional-memory abort ব্যবহার করে, যাতে বারবার চেষ্টা করা যায়)। - কিন্তু probe array-র cache state ততক্ষণে বদলে গেছে — একই timing
attack দিয়ে
secret-এর মান বের করা যায়।
| Spectre | Meltdown | |
|---|---|---|
| কী বিভ্রান্ত হয় | Branch predictor | কিছুই বিভ্রান্ত হয় না — নিছক timing race |
| ট্রিগার | ভুল-training করা conditional/indirect branch | সরাসরি একটা illegal load instruction |
| Control flow লাগে? | হ্যাঁ — misprediction দরকার | না — সরল straight-line কোডেও ঘটে |
| কোন CPU-তে | প্রায় সব modern speculative CPU | মূলত নির্দিষ্ট Intel ডিজাইন (permission check দেরিতে করে) |
| প্রধান mitigation | Retpoline, predictor isolation | KPTI (page table আলাদা করা) |
Retpoline — Spectre v2-এর জন্য একটা software কৌশল
Indirect branch predictor-কে সরাসরি নিরাপদ করা কঠিন (hardware
redesign লাগে, যা microcode দিয়ে আংশিক সম্ভব — নিচে দেখুন)। Google
ইঞ্জিনিয়াররা একটা চতুর বিকল্প বের করেন — retpoline
(“return trampoline”)। মূল ধারণা: indirect jump/call ব্যবহার না
করে, target address-কে stack-এ বসিয়ে একটা ret instruction দিয়ে
“return” করা।
call set_up_target ; ধাপ ১ -- একটা harmless call
capture_spec:
pause ; ধাপ ২ -- speculative execution যদি এখানে
lfence ; এসে পড়ে, এখানেই স্পিন করে থাকবে,
jmp capture_spec ; কোনো ক্ষতি না করে
set_up_target:
mov [rsp], target_addr ; আসল target address stack-এ বসানো
ret ; RSB prediction ব্যবহার করে "return"কেন এটা কাজ করে: ret instruction-এর target predict করতে CPU
সাধারণত একটা আলাদা, ছোট hardware structure ব্যবহার করে — Return
Stack Buffer (RSB), যা সাম্প্রতিক call-গুলোর সাথে মিলিয়ে return
address predict করে, indirect-branch predictor-এর মতো attacker-নিয়ন্ত্রিত
poisoning-এর শিকার সাধারণত হয় না। যেহেতু ঠিক তার আগেই একটা call set_up_target execute হয়েছে, RSB প্রত্যাশা করে এই ret সেই
call-এর ঠিক পরের instruction-এ (capture_spec) ফিরবে — তাই
speculatively CPU সেই harmless spin loop-এ ঢুকে পড়ে, attacker-এর
বেছে নেওয়া কোনো gadget-এ না। ততক্ষণে আসল target address (memory-তে
লেখা) resolve হয়ে যায়, আর ret তার architecturally সঠিক গন্তব্যে
(আসল target) পৌঁছায় — কোনো ভুল ফলাফল হয় না, শুধু speculative পথে
কোনো বিপজ্জনক কোড execute হওয়ার সুযোগ থাকে না।
Microcode mitigation — hardware-স্তরের patch
Retpoline ছাড়াও Intel/AMD firmware/microcode update দিয়ে নতুন control প্রকাশ করেছে যা OS/hypervisor ব্যবহার করতে পারে:
- IBRS (Indirect Branch Restricted Speculation) — একটা privilege level থেকে আরেকটাতে গেলে (যেমন user→kernel, বা VM→hypervisor) indirect branch predictor-কে আগের context-এর history ব্যবহার করা থেকে বিরত রাখে।
- IBPB (Indirect Branch Predictor Barrier) — একটা explicit “flush” — predictor-এর history সম্পূর্ণ মুছে ফেলে একটা trust boundary পার হওয়ার সময় (যেমন process context switch)।
- STIBP (Single Thread Indirect Branch Predictors) — একই physical core-এ দুটো hardware thread (hyperthreading/SMT) যাতে একে অপরের indirect branch predictor state না পড়তে বা বদলাতে পারে, তা নিশ্চিত করে।
প্রতিটাই একটা trust boundary পার হওয়ার মুহূর্তে অতিরিক্ত কাজ যোগ করে (predictor state flush/isolate) — যেটা আগে “বিনামূল্যে” ছিল (predictor state যত খুশি carry করা যেত), এখন প্রতিটা crossing-এ একটা measurable দাম।
উদাহরণ
একটা সম্পূর্ণ timeline — cycle-বাই-cycle
নিচের ট্রেসটা Spectre v1-এর একটা সরলীকৃত timeline, দেখায় কোন ধাপ architectural আর কোনটা শুধু microarchitectural।
- Branch predict: takenঅনেকবার train করা predictor, Strongly Taken state
- array1[x] speculatively fetch/decode/executeসত্যিকারের load unit ব্যবহার হয় — x out-of-bounds, secret byte S পড়ে ফেলে
- array2[S × 4096] speculatively executeসত্যিকারের cache access — probe array-র একটা নির্দিষ্ট লাইন L1/L2-এ লোড হয়
- ROB entry: pendingফলাফল physical register-এ বসে আছে, architectural state এখনো অস্পৃষ্ট
- বাস্তব comparison resolve হয়bounds check মিথ্যা প্রমাণিত — x আসলে সীমার বাইরে
- Misprediction ধরা পড়ে → ROB flushসব pending ROB entry বাতিল — architectural register/memory-তে কোনো ছাপ নেই
- Cache state persist করেprobe array-র সেই একটা লাইন এখনো cache-এ — flush এটা ছোঁয়নি
- Attacker: normal কোডে timing measurement২৫৬টা probe entry-র সময় মেপে দ্রুততাটা খুঁজে বের করা — এটাই S
লক্ষ্য করুন — architectural rollback ঠিক ততটাই দ্রুত আর পরিষ্কার যতটা branch-prediction লেসনে দেখানো হয়েছিল। কোনো correctness bug নেই। শুধু শেষ দুটো ধাপই নতুন — আর সেগুলো ROB-এর দায়িত্বের বাইরে, সম্পূর্ণ ভিন্ন hardware structure (cache) নিয়ে।
নিজে চালিয়ে দেখুন
Raw cache-timing gap নিজে মাপুন — side channel-এর ভিত্তি
// cache_timing.c -- শুধু timing gap পরিমাপ, কোনো speculative gadget না
#include \<stdio.h>
#include \<stdlib.h>
#include \<stdint.h>
#include \<time.h>
#include \<string.h>
#define STRIDE 4096 // এক page দূরত্ব, prefetcher এড়াতে
#define ENTRIES 16 // demo-র জন্য ছোট রাখা হলো
static inline uint64_t now_ns(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * 1000000000ULL + ts.tv_nsec;
}
int main(void) {
unsigned char *probe = aligned_alloc(4096, ENTRIES * STRIDE);
memset(probe, 1, ENTRIES * STRIDE); // সব page প্রথমে touch/allocate
// ১. পুরো probe array cache থেকে বের করে দিন (বড় buffer touch করে)
size_t junk_size = 64 * 1024 * 1024;
unsigned char *junk = malloc(junk_size);
memset(junk, 2, junk_size);
for (size_t i = 0; i \< junk_size; i += 64) junk[i]++; // cache flush-এর মতো প্রভাব
// ২. ইচ্ছাকৃতভাবে শুধু entry #7 touch করি (আসল আক্রমণে এটাই secret byte হতো)
volatile unsigned char sink = probe[7 * STRIDE];
// ৩. এখন প্রতিটা entry-র access time মাপি
printf("%-8s %s\n", "entry", "time (ns)");
for (int i = 0; i \< ENTRIES; i++) {
uint64_t t0 = now_ns();
volatile unsigned char v = probe[i * STRIDE];
uint64_t dt = now_ns() - t0;
(void)v;
printf("%-8d %llu\n", i, (unsigned long long)dt);
}
(void)sink;
return 0;
}gcc -O0 -o cache_timing cache_timing.c && ./cache_timingসাধারণ ফলাফল (মেশিনভেদে সংখ্যা বদলাবে, কিন্তু প্যাটার্ন একই থাকবে):
entry time (ns)
0 142
1 138
2 145
3 140
4 139
5 144
6 141
7 9 ← এইটাই আগে touch করা হয়েছিল — বাকিদের তুলনায় ১৫× দ্রুত
8 143
9 137
...Entry #7 বাকিদের থেকে দশ গুণের বেশি দ্রুত — কারণ সেটাই একমাত্র এন্ট্রি যেটা আগে থেকে cache-এ ছিল। এই একটা সংখ্যাই একটা “gap” — কোন entry touch হয়েছিল তা বলে দিচ্ছে, শুধু timing দেখে, কোনো architectural trace ছাড়াই। Spectre/Meltdown এই একই primitive-কে নেয়, শুধু “কোন entry touch হবে” নির্ধারণ করে secret data দিয়ে, আর “touch” ঘটে একটা speculative (later-discarded) instruction থেকে।
L1 cache hit বনাম DRAM miss-এর মধ্যে timing gap সত্যিই measurable এবং consistent — এটাই সেই raw physical signal যা Spectre/Meltdown-এর মতো side channel আক্রমণ পড়ে (এই experiment নিজে কোনো exploit না, শুধু underlying primitive)।
নিজে বানান
একটা Hit/Miss Classifier বানান — defender-এর মূল টুল
- উপরের cache_timing.c প্রসারিত করে বহুবার (১০০০ iteration) প্রতিটা entry-র সময় মাপুন এবং median বের করুন, single-measurement noise কমাতে
- Cache hit-এর typical সময় আর cache miss-এর typical সময় আলাদা করে ছাপান
- দুইটার মাঝামাঝি একটা threshold বেছে নিন (যেমন average-এর half-way point)
- সেই threshold ব্যবহার করে প্রতিটা entry-কে HIT বা MISS হিসেবে classify করে দেখান classifier সঠিকভাবে touch করা entry-টা identify করছে কি না
- threshold বদলে (খুব কঠোর বা খুব শিথিল) দেখুন false positive/negative কীভাবে বদলায়
মূল ধারণা: security researcher (আর দুর্ভাগ্যবশত attacker-ও) ঠিক এই ধরনের classifier বানান — raw timing number থেকে একটা binary সিদ্ধান্তে (hit/miss) পৌঁছানোর জন্য। এটা defensive research-এও গুরুত্বপূর্ণ — একটা সিস্টেম কতটা “leaky” তা যাচাই করতে ঠিক এই টুল লাগে।
#include \<stdio.h>
#include \<stdlib.h>
#include \<stdint.h>
#include \<time.h>
#include \<string.h>
#define STRIDE 4096
#define ENTRIES 16
#define REPEATS 1000
static inline uint64_t now_ns(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * 1000000000ULL + ts.tv_nsec;
}
int cmp_u64(const void *a, const void *b) {
uint64_t x = *(const uint64_t*)a, y = *(const uint64_t*)b;
return (x > y) - (x \< y);
}
uint64_t median_access_time(volatile unsigned char *addr) {
uint64_t samples[REPEATS];
for (int r = 0; r \< REPEATS; r++) {
uint64_t t0 = now_ns();
volatile unsigned char v = *addr;
samples[r] = now_ns() - t0;
(void)v;
}
qsort(samples, REPEATS, sizeof(uint64_t), cmp_u64);
return samples[REPEATS / 2];
}
int main(void) {
unsigned char *probe = aligned_alloc(4096, ENTRIES * STRIDE);
memset(probe, 1, ENTRIES * STRIDE);
size_t junk_size = 64 * 1024 * 1024;
unsigned char *junk = malloc(junk_size);
memset(junk, 2, junk_size);
for (size_t i = 0; i \< junk_size; i += 64) junk[i]++;
volatile unsigned char sink = probe[11 * STRIDE]; // শুধু #11 touch
uint64_t times[ENTRIES];
uint64_t sum = 0;
for (int i = 0; i \< ENTRIES; i++) {
times[i] = median_access_time(&probe[i * STRIDE]);
sum += times[i];
}
uint64_t threshold = sum / ENTRIES / 2; // সরল heuristic
printf("threshold = %llu ns\n\n", (unsigned long long)threshold);
for (int i = 0; i \< ENTRIES; i++) {
const char *verdict = times[i] \< threshold ? "HIT " : "MISS";
printf("entry %2d : %6llu ns [%s]\n", i,
(unsigned long long)times[i], verdict);
}
(void)sink;
return 0;
}প্রত্যাশিত আউটপুট আকৃতি:
threshold = 78 ns
entry 0 : 145 ns [MISS]
entry 1 : 139 ns [MISS]
...
entry 11 : 8 ns [HIT ]
...
entry 15 : 142 ns [MISS]নিজে বাড়ান:
- Threshold-টা fixed constant না রেখে dynamic bimodal clustering-এ (k-means style, ২টা cluster) বসিয়ে দেখুন — আরও robust হয় কি না
sched_setaffinityদিয়ে প্রোগ্রামটাকে একটা নির্দিষ্ট core-এ pin করে দেখুন measurement-এর noise কমে কি না (OS scheduler-এর হস্তক্ষেপ কমানো)- অন্য একটা process পাশাপাশি চালিয়ে (background load) দেখুন classifier-এর accuracy কতটা কমে যায় — বাস্তব-জগতের noise-এর একটা ছোট সিমুলেশন
বাস্তব সিস্টেমে
Spectre/Meltdown-এর পরে বাস্তব জগত
-
সম্মিলিত প্রকাশ (৩ জানুয়ারি, ২০১৮) — Google Project Zero, একাডেমিক গবেষক দল, আর Intel/AMD/ARM একসাথে সমন্বিতভাবে ঘোষণা দেয়। CVE-2017-5753 (Spectre v1), CVE-2017-5715 (Spectre v2), CVE-2017-5754 (Meltdown) — কার্যত প্রতিটা আধুনিক CPU vendor-কে প্রভাবিত করে, শুধু একটা কোম্পানির bug না।
-
KPTI (Kernel Page Table Isolation) — Meltdown-এর জন্য মূল OS-স্তরের মিটিগেশন। আগে performance-এর জন্য kernel memory প্রতিটা user process-এর page table-এই map করা থাকত (শুধু permission bit দিয়ে সুরক্ষিত) — Meltdown সেই permission bit-কে বাইপাস করত। KPTI kernel আর user memory-র জন্য সম্পূর্ণ আলাদা page table রাখে, তাই speculatively পড়ার মতো kernel ঠিকানাই user-mode-এ mapped থাকে না। দাম: প্রতিটা syscall/interrupt-এ page table switch (CR3 reload) — PCID (Process-Context ID) ছাড়া এতে পুরো TLB flush লাগত, তাই syscall-heavy workload-এ (database, network I/O) ৫-৩০% পর্যন্ত ধীরগতি রিপোর্ট হয়েছিল প্রাথমিক patch-এ।
-
Browser-এর timer precision কমানো — JavaScript-এর মধ্যে থেকেও Spectre-style আক্রমণ সম্ভব প্রমাণিত হয়েছিল (একটা malicious webpage অন্য tab-এর memory পড়ার চেষ্টা করতে পারত)। Chrome আর Firefox ২০১৮-এ
performance.now()-এর resolution কমিয়ে দেয় (microsecond থেকে millisecond-স্তরে), আর সাময়িকভাবেSharedArrayBuffer(যা high-resolution timing তৈরির একটা পরোক্ষ উপায় দিত) বন্ধ রাখে। -
Site Isolation (Chrome) — Spectre-এর ঝুঁকি কমাতে Chrome ভিন্ন ভিন্ন website-কে সম্পূর্ণ আলাদা OS process-এ চালানো শুরু করে, যাতে একটা malicious site-এর JavaScript অন্য site-এর memory একই address space-এ speculative read করতে না পারে — একটা architecture-level পরিবর্তন, শুধু code patch না।
-
MDS/RIDL/Fallout/ZombieLoad পরিবার (২০১৯) — Meltdown-fix-এর পরেও transient execution-এর অন্য hardware buffer (store buffer, fill buffer, load port) থেকে একই ধরনের leak সম্ভব প্রমাণিত হয়। দেখায় Spectre/Meltdown একক ঘটনা না, একটা পুরো vulnerability class — এখনো active security research-এর বিষয়।
-
Cloud provider-দের mass patching — AWS, Google Cloud, Azure — সবাইকে জরুরি ভিত্তিতে হাইপারভাইজার patch করতে হয়েছিল, কারণ multi-tenant cloud-এ (একই physical CPU-তে বিভিন্ন গ্রাহকের VM পাশাপাশি) এই আক্রমণ VM boundary পার হয়ে অন্য গ্রাহকের ডেটা পড়ার ঝুঁকি তৈরি করত — একটা single-tenant বাগের চেয়ে অনেক বেশি জরুরি।
-
Constant-time / masking কোড প্যাটার্ন — নিরাপত্তা-সংবেদনশীল কোডে (cryptographic library) এখন array-index-এর উপর explicit masking ব্যবহার করা হয় (
idx &= (idx \< size) ? all_ones : 0) — compiler-কে branch তৈরি করতে না দিয়ে, যাতে speculative bounds-check bypass-এর কোনো সুযোগই না থাকে।
যে ভুলগুলো সবাই করে
“Misprediction rollback speculative execution-এর সবকিছু মুছে ফেলে — কোনো ছাপ থাকে না।”
এই পুরো লেসনের বিপরীত এই দাবিটাই। Rollback শুধু architectural state (register, memory, flags) পরিষ্কার করে — কারণ ROB শুধু সেটাই commit করে। Microarchitectural state (cache-এর বিষয়বস্তু, TLB entry, branch predictor history) rollback-এর আওতার বাইরে, কারণ সেগুলো ROB-এর দায়িত্বই না। Speculative execution execute হওয়ার সময় যা কিছু hardware-এ physically ঘটে গেছে, তা physically ঘটেই গেছে — শুধু software থেকে সেটা সরাসরি “দেখা” যায় না, timing দিয়ে পরোক্ষে measure করা যায়।
“Spectre আর Meltdown আসলে একই bug, শুধু দুই নামে ডাকা হয়।”
সম্পূর্ণ ভিন্ন mechanism, যদিও একই দিনে প্রকাশ পেয়েছিল আর একই ধরনের cache-timing side channel দিয়ে শেষ হয়। Spectre branch predictor-কে ভুল-training করে একটা misprediction তৈরি করে (control-flow-নির্ভর)। Meltdown কোনো predictor বিভ্রান্ত করে না — এটা শুধু out-of-order execution-এ data forwarding, permission-check-এর চেয়ে দ্রুত হওয়ার একটা race exploit করে (straight-line কোডেও ঘটতে পারে, কোনো branch লাগে না)। এই কারণেই তাদের mitigation-ও ভিন্ন — retpoline মূলত Spectre v2-কে লক্ষ্য করে, KPTI মূলত Meltdown-কে।
“এটা একটা সফটওয়্যার বাগ, একটা OS প্যাচ দিয়ে পুরোপুরি ঠিক করা যায়।”
আংশিক সত্য (Meltdown-এর জন্য KPTI বেশ কার্যকর একটা OS-level fix), কিন্তু Spectre-শ্রেণির সমস্যা মূলত speculative execution-এর নকশাগত আচরণ-এর ফল, যা প্রতিটা আধুনিক high-performance CPU-তেই কোনো না কোনো রূপে থাকে (কারণ speculation বাদ দিলে performance devastating হারে কমে যায়, দুই lesson আগে misprediction penalty আলোচনায় দেখা হয়েছে)। তাই সমাধান স্তরে-স্তরে ছড়ানো — microcode update (IBRS/IBPB), compiler mitigation (retpoline), OS-level isolation (KPTI, site isolation), আর application-level সতর্কতা (constant-time কোড)। ২০১৯-এ MDS/RIDL-এর মতো নতুন variant খুঁজে পাওয়া প্রমাণ করে এই “একবার প্যাচ করলেই শেষ” ধারণাটা ভুল — এটা একটা চলমান research area।
“নিরাপত্তার জন্য speculative execution পুরোপুরি বন্ধ করে দেওয়াই সবচেয়ে নিরাপদ সমাধান।”
তাত্ত্বিকভাবে সত্য (speculation না থাকলে এই পুরো আক্রমণ শ্রেণিই
অসম্ভব), কিন্তু ব্যবহারিকভাবে বিপর্যয়কর। computer-architecture/branch-prediction
লেসনে দেখা হয়েছে একটা deep pipeline-এ misprediction penalty কয়েক
ডজন cycle পর্যন্ত হতে পারে — speculation ছাড়া প্রতিটা branch-এ
CPU-কে stall করতে হতো resolve হওয়া পর্যন্ত, যা modern CPU-র
throughput-কে বহুগুণ কমিয়ে দিত (early, non-speculative CPU-র
তুলনায় আধুনিক CPU-র বেশিরভাগ speedup-ই speculation-নির্ভর কৌশল
থেকে আসে)। তাই বাস্তব mitigation-গুলো speculation বন্ধ করে না
— বরং speculation-কে “নিরাপদ” করার চেষ্টা করে (predictor isolation,
gadget-blocking কোড প্যাটার্ন) যাতে গতির লাভ রেখেই leak-এর পথ বন্ধ
করা যায়। এটাই এই লেসনের কেন্দ্রীয় trade-off — নিরাপত্তা vs গতি,
কোনোটাই বিনামূল্যে না।
বুঝেছেন কি না দেখুন
1একজন সহকর্মী বলে, “ROB precise exception আর misprediction rollback
নিশ্চিত করে, তাই speculative execution সম্পূর্ণ নিরাপদ — কোনো তথ্য
leak হতে পারে না।” এই যুক্তিতে ঠিক কোন ধাপে ভুল আছে?
যুক্তি
ভুলটা “নিরাপদ” শব্দের পরিধি নিয়ে। ROB সত্যিই একটা শক্তিশালী গ্যারান্টি দেয় — কিন্তু শুধু architectural correctness-এর জন্য: রেজিস্টার আর memory-তে শুধু সঠিক, non-speculative ফলাফলই দেখা যাবে। এই গ্যারান্টি কখনোই microarchitectural side effect (cache state, TLB state, predictor history) নিয়ে কিছু বলে না — কারণ সেগুলো ROB-এর নিয়ন্ত্রণেই নেই, সেগুলো সম্পূর্ণ ভিন্ন hardware structure।
তাই সঠিক বক্তব্য হবে: “ROB নিশ্চিত করে প্রোগ্রামের ফলাফল কখনো ভুল হবে না। কিন্তু এটা কোনো গ্যারান্টি দেয় না যে speculative execution-এর কোনো পরোক্ষ, timing-ভিত্তিক পর্যবেক্ষণযোগ্য প্রভাব থাকবে না।” এই পার্থক্যটাই ঠিক ২০১৮ সালে গবেষকরা খুঁজে বের করেছিলেন — সবাই প্রথম গ্যারান্টিটা জানত, কিন্তু দ্বিতীয়টার গুরুত্ব প্রায় কেউ বোঝেনি।
2নিচের কোডটা Spectre v1-এর জন্য কেন সুনির্দিষ্টভাবে দুর্বল তা ব্যাখ্যা
করুন — attacker-কে ঠিক কোন দুইটা কাজ (ক্রমানুসারে) করতে হবে?
int lookup(int idx) {
if (idx \< table_size) {
return secret_table[idx];
}
return -1;
}
প্রয়োগ
int lookup(int idx) {
if (idx \< table_size) {
return secret_table[idx];
}
return -1;
}এই কোডটা exactly Spectre v1-এর canonical প্যাটার্ন — একটা bounds check, তারপর array access, যেখানে check-এর ফলাফল branch predictor দিয়ে predict হয়।
ধাপ ১ — Train। Attacker বহুবার lookup(idx)-কে বৈধ, ছোট
idx (যেমন idx = 2) দিয়ে কল করে। প্রতিবার idx \< table_size
সত্যি হয়, branch taken হয় — predictor “এই branch সবসময় taken”
এই সিদ্ধান্তে দৃঢ়ভাবে পৌঁছায় (Strongly Taken state)।
ধাপ ২ — Exploit। Attacker এবার একটা বড়, out-of-bounds idx
দিয়ে কল করে, এমন একটা মান যা secret_table + idx কে attacker-এর
আগ্রহের কোনো memory ঠিকানার (হয়তো একই প্রোগ্রামের অন্য sensitive
ডেটা) দিকে নিয়ে যায়। যেহেতু predictor অত্যন্ত confident, CPU actual
comparison সম্পূর্ণ হওয়ার আগেই speculatively secret_table[idx]
পড়ে ফেলে — এই out-of-bounds read-টাই ফাঁক।
সেই পড়া মান পরে (এই ছোট উদাহরণে না দেখানো হলেও, বাস্তব exploit-এ) একটা probe array index করতে ব্যবহৃত হবে, যাতে cache-timing দিয়ে পরে সেই মান উদ্ধার করা যায় — ঠিক এই লেসনের mechanism অংশে দেখানো পদ্ধতিতে। মূল শিক্ষা: শুধু bounds check থাকা যথেষ্ট না যদি সেই check predictable আর training-যোগ্য হয়।
3Meltdown-এ কোনো branch misprediction লাগে না, তবু এটাকে “speculative
execution-এর সমস্যা” বলা হয় কেন? Out-of-order execution আর speculative
execution কি তাহলে একই জিনিস?
যুক্তি
না, একই জিনিস না — কিন্তু গভীরভাবে সম্পর্কিত, আর Meltdown ঠিক সেই সম্পর্কের ফাঁকে বাস করে।
Out-of-order execution মানে instruction-গুলো program order না মেনে, dependency অনুযায়ী যে যখন প্রস্তুত তখন execute হওয়া। Speculative execution মানে এমন কোনো instruction execute করা যার “চালানো উচিত কি না” এখনো নিশ্চিত না (branch resolve না হওয়া, বা এখানে — permission check সম্পূর্ণ না হওয়া)।
Meltdown-এ illegal load-টা speculative, কারণ CPU জানে না (এখনো নিশ্চিত হয়নি) এই load আসলে অনুমোদিত কি না — permission check এখনো pending। CPU out-of-order নীতি মেনে এই অনিশ্চয়তা উপেক্ষা করে dependent instruction-গুলোকে এগিয়ে যেতে দেয়, কারণ ডিজাইন priority ছিল “যদি অনুমোদিত হয়, তাহলে সময় বাঁচাও” — ঠিক branch prediction-এর মতোই একটা bet, শুধু bet-টা “branch কোনদিকে যাবে” না, বরং “এই load কি আসলে অনুমোদিত হবে” তার উপর। তাই এটা branch-prediction-নির্ভর না হলেও, একই বৃহত্তর ধরনের কৌশল — অনিশ্চিত ফলাফলের উপর bet ধরে আগেভাগে কাজ করা — যাকে সাধারণভাবে এখন transient execution বলা হয় (Spectre আর Meltdown দুটোই এই ছাতার নিচে পড়ে)।
4একটা browser vendor দুটো mitigation-এর মধ্যে বেছে নিতে হবে: (ক)
performance.now()-এর timer resolution millisecond-এ নামিয়ে আনা,
অথবা (খ) সব cross-origin iframe-কে সম্পূর্ণ আলাদা OS process-এ
আলাদা করা (Site Isolation)। নিরাপত্তা আর ব্যবহারিকতার দিক থেকে
দুটোর trade-off তুলনা করুন।
ডিজাইন
performance.now()-এর timer resolution millisecond-এ নামিয়ে আনা,
অথবা (খ) সব cross-origin iframe-কে সম্পূর্ণ আলাদা OS process-এ
আলাদা করা (Site Isolation)। নিরাপত্তা আর ব্যবহারিকতার দিক থেকে
দুটোর trade-off তুলনা করুন।দুটোই বাস্তবে ব্যবহৃত হয়েছে (একে অপরের বিকল্প না, পরিপূরক), কিন্তু তাদের নিরাপত্তা-শক্তি আর দাম ভিন্ন মাত্রার।
(ক) Timer resolution কমানো — সস্তা কিন্তু দুর্বল প্রতিরক্ষা।
এটা আক্রমণের উপকরণ কমায় (নির্ভুল timing measurement কঠিন করে
তোলে) কিন্তু আক্রমণের সুযোগ বন্ধ করে না। Attacker-রা creative
বিকল্প খুঁজে বের করেছে — যেমন SharedArrayBuffer দিয়ে একটা নিজস্ব
counter thread চালিয়ে effectively high-resolution timer বানানো
(যে কারণে ২০১৮-এ SharedArrayBuffer সাময়িকভাবে বন্ধই রাখা হয়েছিল)।
দাম প্রায় শূন্য (শুধু একটা API-এর precision কমানো), কিন্তু প্রতিরক্ষাও
আংশিক।
(খ) Site Isolation — ব্যয়বহুল কিন্তু গভীর প্রতিরক্ষা। প্রতিটা ভিন্ন origin-এর জন্য আলাদা OS process মানে — এমনকি যদি timing measurement নিখুঁতভাবে কাজ করে, attacker-এর JavaScript এখন আর ভিন্ন origin-এর memory-তে একই address space-এ নেই — সেটা পড়ারই কোনো সুযোগ নেই, শুধু কঠিন না। এটা সমস্যার মূলে আঘাত করে (attack surface বন্ধ করে, শুধু signal দুর্বল করে না)। দাম বেশি — প্রতিটা tab/iframe-এর জন্য একটা পুরো OS process মানে অনেক বেশি memory ব্যবহার (প্রতিটা process-এর নিজস্ব overhead), আর process-এর মধ্যে communication (IPC)-এর জন্য অতিরিক্ত জটিলতা।
সিদ্ধান্ত: দুটোই ব্যবহার করা — (ক) কে সস্তা, তাৎক্ষণিক “friction বাড়ানো” হিসেবে, আর (খ)-কে structural, দীর্ঘমেয়াদি সমাধান হিসেবে। এটাই defense-in-depth-এর একটা বাস্তব উদাহরণ — কোনো একটা mitigation একা যথেষ্ট বিশ্বাসযোগ্য না বলে একাধিক স্তর একসাথে বসানো হয়।
5একটা কোম্পানি তাদের সব server-এ retpoline mitigation চালু করার পর
দেখল database workload ২২% ধীর হয়ে গেছে, কিন্তু একটা memory-bound
scientific computing workload প্রায় কোনো পার্থক্য দেখাচ্ছে না। এই
পার্থক্যের কারণ কী?
প্রয়োগ
পার্থক্যের মূল কারণ — retpoline শুধু indirect branch/call-কে ধীর করে, বাকি সব instruction অপ্রভাবিত থাকে। একটা workload কতটা ধীর হবে তা নির্ভর করে সেই workload কতটা indirect-branch-heavy তার উপর, তার memory access pattern-এর উপর না।
Database workload সাধারণত অনেক indirect call ব্যবহার করে — query executor-এর ভেতরে function pointer table (প্রতিটা operator node-এর জন্য আলাদা execute function, virtual dispatch-এর মতো), প্রতিটা syscall (যা kernel-এর নিজস্ব internal indirect dispatch ব্যবহার করে), আর প্রায়ই dynamically-loaded plugin/extension কোড। এই প্রতিটা indirect call এখন retpoline-এর multi-instruction spin-trap sequence দিয়ে বদলে গেছে — বহু বহু বার repeat হওয়া একটা ছোট overhead যোগ করে দেয়, যা মোট সময়ে জমে বড় শতাংশ হয়ে ওঠে।
Scientific computing workload (যেমন একটা tight matrix-multiply loop) সাধারণত indirect branch প্রায় ব্যবহারই করে না — এটা একটা predictable, straight-line বা সহজ conditional-loop-ভিত্তিক কোড, বেশিরভাগ সময় ALU/memory bandwidth-এ ব্যয় হয়, indirect dispatch-এ না। তাই retpoline যোগ করলেও প্রায় কিছুই বদলায় না — কারণ mitigation যে জিনিসটাকে ধীর করে (indirect branch), সেই workload সেটা প্রায় ব্যবহারই করে না।
সাধারণ নিয়ম: একটা mitigation-এর ব্যয় সবসময় নির্ভর করে সেই workload কতটা সেই নির্দিষ্ট mechanism-এর উপর নির্ভরশীল তার উপর — “universal overhead percentage” বলে কিছু হয় না, profile করেই বলা যায়।
এরপর কী
Branch prediction লেসনে আমরা দেখেছিলাম CPU কীভাবে ভবিষ্যতের control flow অনুমান করে (কোন instruction পরে চলবে)। এই লেসনে দেখলাম সেই অনুমান করার ক্ষমতাই একটা নিরাপত্তা ঝুঁকি তৈরি করতে পারে, cache-এর মাধ্যমে।
পরের লেসনে আমরা CPU-র আরেকটা অনুমান করার ক্ষমতা দেখব — এবার control flow না, data access pattern নিয়ে। Hardware prefetcher CPU-র memory access history দেখে অনুমান করে আপনি পরে কোন ডেটা লাগবে, আর সেটা explicitly না চাওয়া সত্ত্বেও আগেভাগে cache-এ নিয়ে আসে। এটাও এক ধরনের speculation — শুধু control flow-র বদলে data address নিয়ে — আর ঠিক এই লেসনের probe array উদাহরণে আমরা কেন সচেতনভাবে ৪০৯৬ byte দূরত্ব ব্যবহার করেছিলাম (prefetcher-কে বিভ্রান্ত করা এড়াতে), তার পূর্ণ ব্যাখ্যা পরের লেসনেই আসছে।
আরও পড়ুন
- Spectre Attacks: Exploiting Speculative Execution — Kocher, Horn, Fogh, Genkin, Gruss, Haas, Hamburg, Lipp, Mangard, Prescher, Schwarz, Yarom · মূল Spectre পেপার (IEEE S&P 2019) — variant 1 (bounds check bypass) ও variant 2 (branch target injection)-এর প্রামাণ্য বিবরণ
- Meltdown: Reading Kernel Memory from User Space — Lipp, Schwarz, Gruss, Prescher, Haas, Fogh, Horn, Mangard, Kocher, Genkin, Yarom, Hamburg · USENIX Security 2018 — out-of-order execution আর permission check timing-এর gap-ভিত্তিক আক্রমণ
- Reading privileged memory with a side-channel — Jann Horn, Google Project Zero · মূল আবিষ্কারের ব্লগ পোস্ট, ২০১৮ জানুয়ারি
- Retpoline: A Branch Target Injection Mitigation — Paul Turner, Google · Indirect branch-এর জন্য software mitigation — return stack buffer ব্যবহার করে speculative jump-কে নিরাপদ করা