সরাসরি পর্বে যান
পর্ব ৪১ · ৬২-এর মধ্যে · মডিউল ৭

Speculative execution আর নিরাপত্তা — Spectre, Meltdown আর প্রতিরক্ষা

Speculative Execution and Security — Architectural vs Microarchitectural State, Spectre, Meltdown, KPTI, Retpoline, Barriers and Constant-Time Code

Taught in Bangla, this lesson explains why speculation leaks: CPUs roll back architectural state but not cache and predictor state, opening side channels. It covers Spectre bounds-check bypass and branch target injection, Meltdown, KPTI, retpoline, speculation barriers and index masking, compiler and browser defences, microcode and OS updates, constant-time code, and practical steps for users, developers and administrators, all conceptually with steppers and no attack code.

এই পর্বে যা শিখবেন

  • Architectural আর microarchitectural state-এর পার্থক্য, আর side channel কী — ধারণার স্তরে
  • Spectre-এর দুটি মূল রূপ (bounds check bypass আর branch target injection) আর Meltdown কেন সম্ভব হয়েছিল — stepper দিয়ে, কোনো আক্রমণের কোড ছাড়া
  • KPTI, retpoline, speculation barrier, index masking, compiler-এর বিকল্প আর browser-এর প্রতিরক্ষা কী রক্ষা করে আর তার দাম কোথায়
  • Constant-time তুলনা কীভাবে লিখতে হয়, আর KPTI-র খরচ অনুমিত মান থেকে কীভাবে হিসাব করা যায়
  • ব্যবহারকারী, developer আর system administrator-এর জন্য বাস্তব করণীয়, আর গতি বনাম নিরাপত্তার নকশাগত শিক্ষা

এই পর্বের সীমা। এখানে বিষয়টি পাঠ্যবইয়ের মতো, শুধু প্রতিরক্ষার দিক থেকে আলোচনা করা হয়েছে: দুর্বলতা কেন তৈরি হলো আর কীভাবে ঠেকানো হয়। আক্রমণ চালানোর কোড, সময় মাপার কৌশল বা কোনো প্রতিরক্ষা এড়ানোর পথ এই পর্বে নেই, আর থাকবেও না।

১ · দুই রকম অবস্থা — architectural বনাম microarchitectural

পর্ব ৩৭-এ (branch prediction) দেখেছেন, CPU শাখার ফল জানার আগেই অনুমান করে এগিয়ে যায়। পর্ব ৪০-এ (out-of-order execution) এই ধারণা আরও বড় হয়: শত খানেক instruction ক্রমের বাইরে চলে, আর reorder buffer শেষে প্রোগ্রাম-ক্রমে ফল জমা (commit) করে। অনুমানের ভিত্তিতে আগেভাগে কাজ করার এই পদ্ধতির নাম speculative execution. অনুমান ভুল হলে flush — ভুল পথের সব ফল ফেলে দেওয়া হয়, যেন কিছুই ঘটেনি।

“যেন কিছুই ঘটেনি” — এই কথাটি কতটা সত্য? উত্তর নির্ভর করে আমরা কোন অবস্থার কথা বলছি তার ওপর। CPU-র অবস্থা দুই স্তরের:

  • Architectural state — ISA যা প্রতিশ্রুতি দেয়: register, PC, মেমরির বিষয়বস্তু, CSR. প্রোগ্রাম instruction দিয়ে এগুলো সরাসরি পড়তে পারে। ভুল অনুমানের পর এগুলো নিখুঁতভাবে আগের অবস্থায় ফেরে — এটাই precise exception আর flush-এর প্রতিশ্রুতি (পর্ব ৩৮, precise বনাম imprecise).
  • Microarchitectural state — নির্দিষ্ট chip-এর ভেতরের ব্যবস্থা, যা ISA-তে লেখা নেই: cache-এ কোন লাইন আছে, branch predictor-এর counter আর BTB-তে কী শেখা হয়েছে, TLB-তে কোন ঠিকানার অনুবাদ রাখা আছে। এগুলো শুধু গতিকে প্রভাবিত করে, ফলকে নয় — তাই ভুল অনুমানের পর এগুলো ফেরানোর দরকার আছে বলে কেউ ভাবেনি।

একটি রেস্তোরাঁর রান্নাঘর ভাবুন। ভিড়ের সময় অভিজ্ঞ বাবুর্চি আন্দাজ করে নেন, “টেবিল ৫ সম্ভবত কাচ্চি বিরিয়ানিই চাইবে” — অর্ডার আসার আগেই আলমারি থেকে বিরিয়ানির হাঁড়ি নামিয়ে চুলার পাশে রাখেন। পরে অর্ডার এল খিচুড়ি। বাবুর্চি বিরিয়ানির প্লেট বাতিল করলেন — ক্যাশ মেমোতে কিছু লেখা হয়নি, টেবিলে কিছু যায়নি (architectural state অক্ষত)। কিন্তু হাঁড়িটা এখনো চুলার পাশে (microarchitectural state). রান্নাঘরে উঁকি দেওয়া কেউ দেখবে না কে কী খেল, কিন্তু খেয়াল করবে — পরের বিরিয়ানির অর্ডার অস্বাভাবিক দ্রুত বেরোল। সেখান থেকে আন্দাজ করা যায়, একটু আগে বাবুর্চি বিরিয়ানির কথা ভেবেছিলেন। এই “আন্দাজ”-ই পুরো সমস্যার মূল।

Architectural state — ISA যা দেখায় register, PC মেমরি CSR, flag rollback ✓ Microarchitectural state — chip-এর ভেতরের cache predictor, BTB TLB, buffer ভুল পথের চিহ্ন থেকে যায় ✗ গতির ছাপ = side channel ISA শুধু ফলের প্রতিশ্রুতি দেয়, সময়ের নয়
ছবি ৪১.১ — ভুল অনুমানের পর ওপরের স্তর নিখুঁতভাবে ফেরে, নিচের স্তর নয়। নিচের স্তরের অবস্থা গতিতে ছাপ ফেলে।

Side channel — তথ্য বেরোনোর পাশের পথ

প্রোগ্রাম cache-এর ভেতর সরাসরি পড়তে পারে না। কিন্তু একটি ডেটা cache-এ থাকলে তা পড়া দ্রুত, না থাকলে ধীর — এই পার্থক্য প্রায় শতগুণ হতে পারে (কেন, তা পর্ব ৪৪-এ, মেমরি হায়ারার্কি)। ফলে গতির মধ্যেই ভেতরের অবস্থার ছাপ থাকে। যে পথে কোনো ব্যবস্থার মূল কাজের বাইরের প্রভাব — সময়, শক্তি-খরচ, শব্দ, বিদ্যুৎ-চুম্বকীয় বিকিরণ — থেকে গোপন তথ্য আন্দাজ করা যায়, তাকে বলে side channel. যখন ছাপটি সময়ের মধ্যে, তখন বিশেষভাবে timing attack.

ধারণাটি নতুন নয়। ১৯৯৬ সালে Paul Kocher দেখান, RSA-র মতো cryptography-র হিসাবে সময় গোপন key-এর ওপর নির্ভর করলে সময় মেপেই key আন্দাজ করা যায়। ২০০৫ সালের দিকে কয়েকটি গবেষণা দেখায়, AES-এর টেবিল-ভিত্তিক বাস্তবায়নে কোন টেবিল-ঘর cache-এ আছে, তা থেকেও key-র তথ্য বেরোয়। অর্থাৎ ২০১৮-র আগেই জানা ছিল: cache ভাগাভাগি করা মানে তথ্য ভাগাভাগির ঝুঁকি. নতুন যা এল তা হলো — speculative execution নিজেই গোপন ডেটা ছুঁয়ে সেই ছাপ তৈরি করতে পারে, এমন কোডের পথে যা প্রোগ্রাম আসলে কখনো চালায়নি।

মূল ধারণা

ISA ফলের চুক্তি, সময়ের নয়। ভুল অনুমানের পথে চলা instruction-কে transient execution বলা হয় — ক্ষণস্থায়ী, কারণ তার ফল কখনো commit হয় না। কিন্তু সে microarchitectural state বদলাতে পারে, আর সেই বদল সময়ের মাধ্যমে বাইরে থেকে আন্দাজযোগ্য। Spectre-পরিবারের সব সমস্যার মূল এই এক বাক্যে।

২ · Spectre v1 — bounds check-এর শাখা ভুল অনুমান হলে

Spectre প্রকাশিত হয় ২০১৮ সালের জানুয়ারির শুরুতে, Meltdown-এর সাথে একই সময়ে। কয়েকটি স্বাধীন গবেষক দল (একটি বড় প্রযুক্তি প্রতিষ্ঠানের নিরাপত্তা-গবেষণা দল আর কয়েকটি বিশ্ববিদ্যালয়ের গবেষক) প্রায় একই সময়ে আলাদাভাবে এগুলো খুঁজে পান বলে জানা যায়। Spectre একটি দুর্বলতা নয়, একটি পরিবার; প্রথম দুটি রূপ সাধারণত “variant 1” আর “variant 2” নামে পরিচিত।

প্রথম রূপের নাম bounds check bypass. সাধারণ নিরাপদ কোডে array পড়ার আগে bounds check থাকে: index সীমার ভেতরে হলেই পড়া। সমস্যা হলো, এই যাচাইটিও একটি শাখা — আর শাখার ফল জানার আগে CPU অনুমান করে। পাঠ্যবইয়ের ধাঁচটি এমন (একবারই দেখানো হলো, পাশে সংশোধিত রূপ):

// উদাহরণ — পাঠ্যবইয়ের ঝুঁকিপূর্ণ ধাঁচ (চালানোর জন্য নয়)
if (i < n) {                 // bounds check: the predictor may guess "true"
    v = table[data[i]];      // data[i] is read on the guessed path, then used as an index
}

// উদাহরণ — সংশোধিত: index masking (Linux kernel-এর array_index_nospec ধাঁচ)
if (i < n) {
    i = array_index_nospec(i, n);   // i outside 0..n-1 becomes 0, even on a guessed path
    v = table[data[i]];
}

লক্ষ করুন, প্রথম কোডে কোনো ভুল নেই — ISA-র দৃষ্টিতে i সীমার বাইরে হলে ভেতরের লাইন কখনো চলে না। ঝুঁকি দুটি শর্ত একসাথে থাকলে: (১) i বাইরের কেউ ঠিক করতে পারে (যেমন একটি system call-এর argument), আর (২) অনুমানের পথে পড়া মানটি আরেকটি মেমরি-ঠিকানার অংশ হয়। নিচের stepper-এ পুরো ঘটনাটি ধারণার স্তরে, কোনো সংখ্যা বা ঠিকানা ছাড়া। “পরে” চাপুন:

  1. অভ্যাসi < n ✓predictor শেখে
  2. ভুল অনুমানi ≥ nতবু “ভেতরে” ধরে
  3. ক্ষণস্থায়ী কাজdata[i]সীমার বাইরে পড়া
  4. Rollbackflushregister অক্ষত
  5. চিহ্নcacheভেতরে থেকে যায়
  6. প্রতিরক্ষাmask / barrierপথ বন্ধ
অবস্থাস্বাভাবিক ব্যবহার
Architectural      : প্রতিটি ডাকে i সীমার ভেতরে, ঠিক ফল
Microarchitectural : শাখার counter শিখছে "শর্ত সত্য"
অবস্থাসীমার বাইরের index এল
Architectural      : কিছুই বদলায়নি, শাখার ফল এখনো অজানা
Microarchitectural : n-এর মান cache-এ নেই -> তুলনা দেরিতে
                     predictor-এর অনুমান: "ভেতরে"
অবস্থাঅনুমানের পথে কাজ
Architectural      : কিছুই commit হয়নি
Microarchitectural : সীমার বাইরের মান পড়া হলো,
                     সেই মানের ওপর নির্ভর করে একটি cache লাইন আনা হলো
অবস্থাতুলনা শেষ: অনুমান ভুল
Architectural      : ভুল পথের সব ফল ফেলে দেওয়া হলো
                     register, মেমরি ঠিক আগের মতো ✓
Microarchitectural : flush এটি ছোঁয় না
অবস্থাযা থেকে গেল
Architectural      : অক্ষত
Microarchitectural : গোপন মানের ওপর নির্ভরশীল cache-অবস্থা
                     -> গতির পার্থক্যে বাইরে থেকে আন্দাজযোগ্য
অবস্থাbarrier বা index mask থাকলে
Barrier    : তুলনা শেষ না হওয়া পর্যন্ত পরের load অপেক্ষা করে
Index mask : অনুমানের পথেও i সীমার ভেতরে টেনে আনা হয়
ফল         : গোপন মানের ওপর নির্ভরশীল কোনো চিহ্ন তৈরি হয় না
  1. ধাপ ১ — অভ্যাস। কোডটি বারবার বৈধ index দিয়ে ডাকা হয়। প্রতিবার শর্ত সত্য, তাই শাখার predictor শেখে: “এই শাখায় শর্ত সত্য”। এখনো সবকিছু স্বাভাবিক, নিরাপদ।
  2. ধাপ ২ — ভুল অনুমান। এবার সীমার বাইরের একটি index এল। তুলনার জন্য n-এর মান লাগে; সেটি cache-এ না থাকলে আনতে অনেক cycle লাগে। এই সময়টুকু CPU বসে থাকে না — predictor-এর অভ্যাস অনুযায়ী ধরে নেয় শর্ত সত্য।
  3. ধাপ ৩ — ক্ষণস্থায়ী কাজ। অনুমানের পথে ভেতরের লাইন চলে: সীমার বাইরের ঘর থেকে একটি মান পড়া হয়, আর সেই মান আরেকটি টেবিলের index হিসেবে ব্যবহৃত হয়। ফলে কোন cache লাইন আনা হবে, তা ঠিক হয় গোপন মান দিয়ে।
  4. ধাপ ৪ — Rollback. তুলনার ফল এল: শর্ত মিথ্যা, অনুমান ভুল। Reorder buffer ভুল পথের সব instruction ফেলে দেয় — একটিও commit হয়নি, কোনো register বা মেমরিতে কিছু লেখা হয়নি। ISA-র দৃষ্টিতে প্রোগ্রাম সম্পূর্ণ সঠিক।
  5. ধাপ ৫ — চিহ্ন থেকে যায়। কিন্তু ধাপ ৩-এ আনা cache লাইনটি cache-এই রয়ে গেছে, কারণ flush শুধু architectural state ফেরায়। কোন লাইনটি এল, তা নির্ভর করেছিল গোপন মানের ওপর — তাই গতির পার্থক্য থেকে সেই মানের আভাস পাওয়া সম্ভব। এটিই side channel.
  6. ধাপ ৬ — প্রতিরক্ষা। দুটি সরল উপায়: bounds check-এর পরে একটি speculation barrier, যাতে তুলনার ফল না জানা পর্যন্ত পরের load শুরুই না হয়; অথবা index masking, যাতে শাখা ছাড়াই, গাণিতিকভাবে, index সীমার ভেতরে চলে আসে — অনুমানের পথেও। দুই ক্ষেত্রেই গোপন মানের ওপর নির্ভরশীল কোনো চিহ্ন তৈরি হয় না।

Barrier আর masking — কোনটি কখন

Barrier: x86-এ lfence instruction এই কাজে ব্যবহৃত হয় (নির্মাতাদের নির্দেশিকা অনুযায়ী এটি আগের instruction শেষ না হওয়া পর্যন্ত পরেরগুলো শুরু হতে দেয় না); Arm তাদের নির্দেশিকায় CSDB আর পরে SB-এর মতো barrier instruction-এর কথা বলেছে। Barrier সহজ ও নিশ্চিত, কিন্তু দামি — সেই জায়গায় out-of-order-এর পুরো সুবিধা হারায়। তাই সব শাখায় নয়, শুধু ঝুঁকিপূর্ণ জায়গায় বসানো হয়।

Index masking: তুলনার ফল থেকে শাখা ছাড়াই একটি mask বানানো হয় — index বৈধ হলে সব বিট ১, না হলে সব ০ — আর index-এর সাথে AND করা হয়। Mask-এর হিসাব একটি data dependency, অনুমান নয়; তাই অনুমানের পথেও index বাইরে যেতে পারে না। দাম মাত্র কয়েকটি সরল instruction. Linux kernel-এ এই কাজের জন্য array_index_nospec() নামে একটি সহায়ক আছে, আর system call-এর মতো সীমানার কোডে ব্যবহৃত হয়।

৩ · Spectre v2 আর browser — predictor-কে অন্যের হয়ে শেখানো

দ্বিতীয় রূপের নাম branch target injection. পর্ব ৩৭-এ (BHT, BTB আর RAS) দেখেছেন, register থেকে আসা গন্তব্যের লাফ (virtual call, function pointer, switch-এর jump table) অনুমান করতে indirect branch prediction লাগে, আর BTB মনে রাখে “এই শাখা শেষবার কোথায় গিয়েছিল”। এই টেবিলগুলো সাধারণত পূর্ণ ঠিকানা দিয়ে tag করা থাকে না (জায়গা বাঁচাতে), আর একই core-এ চলা ভিন্ন প্রোগ্রাম — এমনকি user আর kernel — একই টেবিল ভাগ করতে পারত।

ধারণাটি তাই: এক প্রোগ্রাম নিজের কোডে বারবার এমন লাফ দেয় যা predictor-এর টেবিলে অন্য প্রোগ্রামের একটি indirect শাখার ঘরের সাথে মিলে যায়। ফলে অন্য প্রোগ্রামের সেই শাখা অনুমানে ভুল গন্তব্যে যায় — সেখানে থাকা বৈধ কিন্তু অপ্রাসঙ্গিক কিছু instruction ক্ষণস্থায়ীভাবে চলে আর cache-এ চিহ্ন রাখে। পর্ব ৩৭-এর return address stack-ও একই রকম ভাগাভাগি করা কাঠামো, তাই পরে return-এর অনুমান নিয়েও একই ধরনের সমস্যার কথা প্রকাশিত হয়েছে। মূল কথা: predictor-এর অবস্থা ভাগ করা থাকলে, এক পক্ষ অন্য পক্ষের অনুমান প্রভাবিত করতে পারে।

রেলস্টেশনের একজন পয়েন্টসম্যান অভ্যাসে চলেন: যে লাইনে গত কয়েকটি ট্রেন গেছে, পরেরটির জন্যও আগেভাগে সেই লাইনে পয়েন্ট বদলে রাখেন। কেউ যদি পরপর কয়েকটি খালি ইঞ্জিন একটি নির্দিষ্ট লাইনে চালিয়ে তাঁর অভ্যাস “শিখিয়ে” দেয়, তাহলে পরের আসল ট্রেনটিও কিছুক্ষণের জন্য সেই লাইনে ঢুকে পড়বে — যতক্ষণ না সিগন্যাল কেবিন থেকে আসল নির্দেশ আসে আর ট্রেন ফিরিয়ে আনা হয়। যাত্রীর গন্তব্য বদলায়নি, কিন্তু ভুল লাইনের প্ল্যাটফর্মে যে কিছুক্ষণ ট্রেন দাঁড়িয়েছিল, তা দূর থেকে দেখা গেছে। সমাধান: প্রতিটি কোম্পানির ট্রেনের জন্য আলাদা অভ্যাসের খাতা, অথবা গুরুত্বপূর্ণ জংশনে অভ্যাসের ওপর ভরসা না করা।

কেন browser-এর JavaScript বিশেষ চিন্তার কারণ ছিল

Browser প্রতিদিন অচেনা website-এর JavaScript চালায়। এতদিন নিরাপত্তার ভিত্তি ছিল sandbox: ভাষার নিয়ম (array-র সীমা যাচাই, pointer না থাকা) আর JIT compiler-এর (চলার সময় JavaScript-কে machine code-এ রূপান্তর করে যে compiler) যাচাই নিশ্চিত করে যে একটি পাতার কোড অন্য পাতার বা browser-এর ডেটা পড়তে পারবে না। আর অনেক সময় ভিন্ন website একই process-এ, একই address space-এ চলত। Spectre দেখায়, ভাষার bounds check-ও একটি শাখা — অনুমানের পথে সেটিও পার হয়ে যেতে পারে। অর্থাৎ একই address space-এর ভেতরে শুধু software-এর নিয়ম দিয়ে গোপনীয়তা রাখা আর নিশ্চিত নয়. Browser নির্মাতারা তাই দ্রুত পদক্ষেপ নেন (অংশ ৬-এ বিস্তারিত): ভিন্ন site আলাদা process-এ রাখা, সূক্ষ্ম timer মোটা করা, আর কিছু সুবিধা সাময়িকভাবে বন্ধ রাখা।

৪ · Meltdown — অনুমতির যাচাই কখন কার্যকর হয়

পর্ব ৩৮-এ (Exception আর interrupt) দেখেছেন, প্রতিটি প্রোগ্রাম চলে user mode-এ, আর operating system-এর kernel চলে supervisor mode-এ। Page table-এর প্রতিটি ঘরে একটি বিট বলে “এই পাতা শুধু supervisor-এর জন্য”। User mode থেকে সেই পাতা পড়ার চেষ্টা করলে page fault — একটি exception, আর kernel প্রোগ্রামটিকে থামিয়ে দেয়।

প্রশ্ন হলো, এই যাচাইয়ের ফল কখন কার্যকর হয়। Precise exception-এর নিয়মে exception নেওয়া হয় instruction যখন retire (commit) হয়, প্রোগ্রাম-ক্রমে (পর্ব ৪০-এর reorder buffer). কিছু out-of-order নকশায় অনুমতি-যাচাইয়ের ফল শুধু একটি “এটি fault করবে” চিহ্ন হিসেবে instruction-এর সাথে লেগে থাকত, আর load-এর ডেটা ততক্ষণে নির্ভরশীল instruction-গুলোর কাছে পৌঁছে যেত। Retire-এ fault ধরা পড়ে, সব ফল ফেলে দেওয়া হয় — কিন্তু মাঝের সেই ক্ষণস্থায়ী কাজ cache-এ চিহ্ন রেখে যেতে পারত। এই ধারণাকেই Meltdown বলা হয়। Spectre-এর মতো বৈধ কোড ভুল পথে চালানোর দরকার এখানে নেই; সমস্যাটি একটি নির্দিষ্ট নকশাগত সিদ্ধান্তে।

  1. User loadkernel পাতাঅনুমতি নেই
  2. যাচাইfault চিহ্নকিন্তু এখনই নয়
  3. নির্ভরশীল কাজtransientcache-এ চিহ্ন
  4. Retirepage faultসব ফেলে দেওয়া
  5. প্রতিরক্ষাআগেই আটকানোবা map-ই নয়
Pipeline-এর ভেতরেload ঢুকল
User mode-এর একটি load-এর ঠিকানা kernel-এর পাতায়
TLB / page table: পাতাটি আছে, কিন্তু supervisor-only
Pipeline-এর ভেতরেআক্রান্ত নকশার আচরণ
অনুমতি-যাচাই  : ব্যর্থ -> ROB-এর ঘরে "fault" চিহ্ন
ডেটা          : তবু ক্ষণিকের জন্য নির্ভরশীল instruction-এর কাছে
exception     : অপেক্ষায়, retire-এর সময়ের জন্য
Pipeline-এর ভেতরেout-of-order জানালা
পরের কয়েকটি instruction মানটি ব্যবহার করে
-> মানের ওপর নির্ভর করে cache-এর অবস্থা বদলায়
(কিছুই commit হয়নি)
Pipeline-এর ভেতরেপ্রোগ্রাম-ক্রমে retire
load ROB-এর মাথায় পৌঁছাল -> page fault নেওয়া হলো
পরের সব ফল ফেলে দেওয়া হলো (precise exception) ✓
cache-এর চিহ্ন: থেকে গেল ✗
দুই স্তরের সমাধানhardware আর software
Hardware (নতুন নকশা): অনুমতি ব্যর্থ হলে ডেটা নির্ভরশীলদের কাছে যায়ই না
Software (KPTI)      : user mode-এ kernel-এর পাতা map-ই করা থাকে না
                       -> পড়ার মতো কোনো অনুবাদই নেই
  1. ধাপ ১ — নিষিদ্ধ ঠিকানায় load. User mode-এর একটি load-এর ঠিকানা kernel-এর পাতায় পড়েছে। পাতাটি page table-এ আছে (কেন, তা অংশ ৫-এ), কিন্তু supervisor-only চিহ্নিত। ISA-র নিয়মে এর ফল একটাই: page fault.
  2. ধাপ ২ — যাচাই হলো, কিন্তু কার্যকর হলো না। আক্রান্ত নকশায় অনুমতি-যাচাই ব্যর্থ হলে instruction-এর ঘরে একটি fault চিহ্ন বসে, কিন্তু exception নেওয়া হয় পরে, retire-এর সময়। ইতিমধ্যে load-এর ডেটা নির্ভরশীল instruction-এর কাছে ক্ষণিকের জন্য পৌঁছে যেতে পারত — দ্রুততার জন্য নেওয়া একটি সিদ্ধান্ত।
  3. ধাপ ৩ — ক্ষণস্থায়ী নির্ভরশীল কাজ। Out-of-order জানালায় পরের কয়েকটি instruction সেই মান ব্যবহার করে, আর মানের ওপর নির্ভর করে cache-এর অবস্থা বদলায়। এখনো কিছুই commit হয়নি — ঠিক Spectre-এর ধাপ ৩-এর মতো।
  4. ধাপ ৪ — Retire-এ fault. Load ROB-এর মাথায় পৌঁছালে page fault নেওয়া হয়, পরের সব ফল ফেলে দেওয়া হয়। Architectural state নিখুঁত — precise exception তার কাজ ঠিকই করেছে। কিন্তু cache-এর চিহ্ন flush ছোঁয় না।
  5. ধাপ ৫ — প্রতিরক্ষা দুই স্তরে। নতুন hardware নকশায় যাচাই ব্যর্থ হলে ডেটা নির্ভরশীলদের কাছে যেতেই দেওয়া হয় না (যেমন শূন্য বা কিছুই না পাঠানো)। পুরোনো chip-এর জন্য software-এর সমাধান KPTI: user mode-এ kernel-এর পাতাগুলো map-ই না রাখা — অনুবাদ না থাকলে ক্ষণস্থায়ী load-ও কিছু পায় না।

কারা আক্রান্ত, আর পরের “পরিবার”

Meltdown সব CPU-তে কাজ করে না; এটি নির্ভর করে নির্দিষ্ট নকশাগত সিদ্ধান্তের ওপর, আর ২০১৮ সালে কিছু নির্মাতার কিছু প্রজন্মের নকশা আক্রান্ত ছিল। কোন মডেল আক্রান্ত, তার তালিকা নির্মাতারা নিজেরা প্রকাশ করেছিল — সঠিক তথ্যের জন্য সেটিই দেখা উচিত, অনুমান বা লোকমুখের তালিকা নয়।

পরের বছরগুলোতে একই মূল ধারণার — transient execution আর microarchitectural চিহ্ন — আরও অনেক রূপ প্রকাশিত হয়, যাদের কয়েকটির নাম Foreshadow, MDS-পরিবার (যেমন ZombieLoad, RIDL), Retbleed, Downfall. প্রতিটির নিজস্ব খুঁটিনাটি আর নিজস্ব সংশোধন আছে; এই পর্বে সেগুলোর বিস্তারিত নেই। শিক্ষার্থীর জন্য গুরুত্বপূর্ণ হলো পরিবারটিকে চেনা: যেখানেই একটি ভাগাভাগি করা ভেতরের কাঠামো (buffer, cache, predictor) ক্ষণস্থায়ী কাজের প্রভাব ধরে রাখে, সেখানেই একই প্রশ্ন ওঠে।

৫ · KPTI — kernel-এর জন্য আলাদা page table

প্রথমে বুঝি, kernel-এর পাতা user-এর page table-এ কেন থাকত। প্রতিটি system call, interrupt বা exception-এ CPU kernel-এ ঢোকে। Page table বদলানো দামি: page table-এর ভিত্তি-ঠিকানা রাখা register (x86-এ CR3, RISC-V-এ satp) বদলালে TLB-র অনেক অনুবাদ অকেজো হয়ে যেতে পারে (TLB-র কাজ পর্ব ৫০-এ, Virtual memory-র hardware; OS-এর দিক থেকে OS কোর্সের TLB পর্বে)। তাই বহু বছর ধরে OS-গুলো প্রতিটি process-এর page table-এর ওপরের অংশে পুরো kernel map করে রাখত, supervisor-only চিহ্ন দিয়ে। Kernel-এ ঢোকা মানে শুধু mode বদল, page table বদল নয় — দ্রুত। Meltdown দেখাল, আক্রান্ত নকশায় এই চিহ্নের ওপর ভরসা করা যায় না।

সমাধান KPTI (kernel page-table isolation). এর ভিত্তি ২০১৭ সালে প্রকাশিত একটি একাডেমিক প্রস্তাব, KAISER — যা মূলত অন্য একটি kernel-সুরক্ষা (kernel-এর ঠিকানা এলোমেলো রাখা) রক্ষার জন্য প্রস্তাবিত হয়েছিল। Meltdown প্রকাশের সময় Linux-এ এটি KPTI নামে যোগ হয়, আর অন্য বড় OS-গুলোও একই ধারণার নিজস্ব রূপ আনে বলে জানা যায়। নিচের stepper-এ ধারণাটি:

আগে: একটি page tableuser অংশ + পুরো kernel (supervisor-only)
Kernel-এর পাতা user mode-এও map করাMeltdown-এর সুযোগ
User page tableuser অংশ + ছোট entry stub মাত্র
Kernel page tableuser অংশ + পুরো kernel
System call → entry stubpage table-এর ভিত্তি বদল
ফেরার আগে আবার বদলuser page table-এ
খরচ: প্রতি প্রবেশ-প্রস্থানে বাড়তি কাজASID/PCID থাকলে কম
  1. আগের ব্যবস্থা। প্রতিটি process-এর একটি page table, যার ওপরের অংশে পুরো kernel map করা, supervisor-only বিট দিয়ে সুরক্ষিত। System call-এ শুধু mode বদলায় — দ্রুত। কিন্তু আক্রান্ত নকশায় ক্ষণস্থায়ী load এই অনুবাদ ব্যবহার করতে পারত।
  2. KPTI: দুটি page table. User mode-এ যে page table চলে, তাতে user-এর সব পাতা কিন্তু kernel-এর প্রায় কিছুই নেই — শুধু kernel-এ ঢোকা আর বেরোনোর ছোট একটি অংশ (entry stub) আর অপরিহার্য কয়েকটি কাঠামো। Kernel-এর নিজস্ব page table-এ সব আছে।
  3. Kernel-এ ঢোকা। System call বা interrupt-এ CPU supervisor mode-এ entry stub-এ যায়। Stub-এর প্রথম কাজগুলোর একটি: page table-এর ভিত্তি-register বদলে kernel page table বসানো। তারপরই আসল kernel কোড চলে।
  4. ফিরে আসা। Kernel কাজ শেষ করে, user mode-এ ফেরার ঠিক আগে আবার user page table বসায়। User কোড যখন চলছে, তখন kernel-এর পাতার কোনো অনুবাদই নেই — ক্ষণস্থায়ী load-ও পড়ার মতো কিছু পায় না।
  5. দাম। প্রতিটি প্রবেশ-প্রস্থানে দুবার page table বদল, আর TLB-র অনুবাদ হারানোর ঝুঁকি। Address-space identifier (ASID; x86-এ PCID) থাকলে TLB-র ঘরগুলো কোন page table-এর তা চিহ্নিত থাকে, তাই বদলের পরও মোছার দরকার কমে — খরচ অনেকটা কমে। যে কাজে system call বা interrupt খুব ঘন, সেখানে দাম সবচেয়ে বেশি চোখে পড়ে।

খরচের মোটা হিসাব — শুধু অনুমিত মান দিয়ে

KPTI-র খরচ কত, তা নির্ভর করে CPU, OS-এর সংস্করণ, ASID আছে কি না আর workload-এর ওপর — একটি সংখ্যা সবার জন্য সত্য নয়। কিন্তু ধরনটি একটি সরল সূত্রে বোঝা যায়: প্রতি সেকেন্ডে kernel-এ কতবার ঢোকা হয়, গুণ প্রতিবারের বাড়তি সময়। নিচের calculator-এর প্রতিটি সংখ্যা অনুমান, শেখানোর জন্য বেছে নেওয়া — কোনো নির্দিষ্ট chip-এর পরিমাপ নয়:

overhead (%) ≈ (প্রতি core-এ প্রতি সেকেন্ডে kernel-প্রবেশ) × (প্রতিবারের বাড়তি সময়, সেকেন্ডে) × 100
// KPTI overhead estimator. EVERY number below is an ASSUMPTION for teaching, not a measurement.
// overhead % = (kernel entries per second per core) x (assumed extra ns per entry+exit) / 1e9 x 100
const extraNs = { "no ASID": 400, "with ASID": 100 };   // ASSUMED extra cost per kernel entry+exit pair
const profiles = [                                      // ASSUMED kernel entries per second per core
  ["number crunching", 1000],
  ["desktop + browser", 20000],
  ["busy web server", 100000],
  ["database, small I/O", 400000],
];
const pct = (rate, ns) => (rate * ns / 1e9 * 100).toFixed(2) + "%";
console.log("ASSUMED VALUES ONLY - measure your own workload before deciding");
console.log("profile".padEnd(21) + "entries/s".padStart(10) + "no ASID".padStart(10) + "with ASID".padStart(11));
for (const [name, rate] of profiles)
  console.log(name.padEnd(21) + String(rate).padStart(10) +
              pct(rate, extraNs["no ASID"]).padStart(10) + pct(rate, extraNs["with ASID"]).padStart(11));
// At what rate does the assumed overhead reach 5% ?
for (const [k, ns] of Object.entries(extraNs))
  console.log(`${k}: 5% overhead at about ${Math.round(0.05 * 1e9 / ns)} entries/s`);

আউটপুট (নির্ধারিত, কারণ সব মান স্থির অনুমান):

ASSUMED VALUES ONLY - measure your own workload before deciding
profile               entries/s   no ASID  with ASID
number crunching           1000     0.04%      0.01%
desktop + browser         20000     0.80%      0.20%
busy web server          100000     4.00%      1.00%
database, small I/O      400000    16.00%      4.00%
no ASID: 5% overhead at about 125000 entries/s
with ASID: 5% overhead at about 500000 entries/s

হিসাবটি সরল গুণ: “busy web server”-এ 100000 × 400 ns = 0.04 s প্রতি সেকেন্ডে, অর্থাৎ 4.00%. সংখ্যাগুলো নিজে বিশ্বাসযোগ্য দাবি নয়, কিন্তু ধরন শিক্ষণীয়: খরচ kernel-প্রবেশের হারের সাথে রৈখিকভাবে বাড়ে। হিসাব-ভারী কাজে (সেকেন্ডে হাজারখানেক প্রবেশ) প্রভাব প্রায় শূন্য, আর ছোট ছোট I/O-ওয়ালা database-এ সবচেয়ে বেশি। ASID থাকলে (এখানে প্রতিবারের খরচ ৪ ভাগের ১ ধরা) একই workload-এ খরচ ৪ ভাগ কমে। শেষ দুই লাইন বলে, এই অনুমানে ৫% ছুঁতে কত ঘন kernel-প্রবেশ লাগে। আসল সিদ্ধান্তের আগে নিজের সিস্টেমে আগে-পরে মেপে দেখা (যেমন benchmark বা perf দিয়ে) — অনুমিত টেবিল নয়।

৬ · প্রতিরক্ষার অন্য স্তরগুলো — retpoline থেকে browser আর microcode

কোনো একটি প্রতিরক্ষা সব রূপ ঠেকায় না। তাই বাস্তবে কয়েকটি স্তর একসাথে কাজ করে — hardware, firmware, OS, compiler, application. নিচের কার্ডগুলোতে প্রধান স্তরগুলো, ধারণার স্তরে:

Retpoline

Retpoline (“return trampoline”) ২০১৮ সালে প্রকাশিত একটি compiler-কৌশল। Indirect লাফকে এমন একটি ক্রমে বদলানো হয়, যাতে CPU-র অনুমান BTB-র বদলে return-এর অনুমানের দিকে যায়, আর সেই অনুমানের পথে থাকে একটি নিরীহ, অসীম অপেক্ষার লুপ। আসল গন্তব্যে পৌঁছানো হয় সঠিকভাবেই, শুধু অনুমানের সুযোগ বন্ধ।

v2-এর বিরুদ্ধে, software

Predictor-এর নিয়ন্ত্রণ

Microcode update আর নতুন chip-এ OS-কে কিছু নিয়ন্ত্রণ দেওয়া হয়: privilege স্তর বা context বদলের সময় indirect predictor-এর শেখা অবস্থা মুছে ফেলা বা আলাদা রাখা, যাতে এক পক্ষের প্রশিক্ষণ অন্য পক্ষে না পৌঁছায়। নতুন নকশায় predictor-এর ঘর privilege স্তর দিয়ে আলাদা রাখার কথা বলা হয়।

v2-এর বিরুদ্ধে, hardware + OS

Barrier আর masking

অংশ ২-এর মতো: ঝুঁকিপূর্ণ bounds check-এর পরে barrier, অথবা শাখাহীন index masking. Kernel আর browser engine-এর নির্দিষ্ট জায়গায় হাতে বসানো হয়; কিছু compiler এটি স্বয়ংক্রিয়ভাবে করার বিকল্প দেয়।

v1-এর বিরুদ্ধে

KPTI

অংশ ৫: user mode-এ kernel map-ই না রাখা। আক্রান্ত পুরোনো chip-এ Meltdown-এর প্রধান software-প্রতিরক্ষা; যেসব নতুন chip hardware-এ সমস্যাটি সারিয়েছে, সেখানে OS সাধারণত এটি বন্ধ রাখে বলে জানা যায়।

Meltdown-এর বিরুদ্ধে

Compiler-এর বিকল্প (সংস্করণভেদে বদলায়)

বড় compiler-গুলো এই প্রতিরক্ষাগুলো স্বয়ংক্রিয়ভাবে যোগ করার বিকল্প এনেছে। উদাহরণ হিসেবে, GCC-তে -mindirect-branch=thunk-ধাঁচের আর Clang-এ -mretpoline বিকল্প retpoline বসায়; LLVM-এ “speculative load hardening” নামে একটি বিস্তৃত (আর দামি) v1-প্রতিরক্ষার বিকল্প আছে; Microsoft-এর C/C++ compiler-এ /Qspectre কিছু v1-ধাঁচের জায়গায় barrier বসায় বলে নথিতে বলা আছে। বিকল্পের নাম, আচরণ আর কভারেজ সংস্করণভেদে বদলায়, আর কোনোটিই সব ঝুঁকিপূর্ণ জায়গা নিশ্চিতভাবে ধরে না — তাই নিজের compiler-এর বর্তমান ডকুমেন্টেশন দেখে নেওয়াই নিয়ম।

Browser-এর প্রতিরক্ষা

  • Site isolation: ভিন্ন website-এর পাতা আলাদা operating-system process-এ চালানো। তখন একটি পাতার কোড অনুমানের পথেও অন্য site-এর ডেটা নিজের address space-এ পায় না — process-এর সীমানা hardware-এর page table দিয়ে রক্ষিত। ডেস্কটপ browser-এ ২০১৮ সালের দিকে এটি ব্যাপকভাবে চালু হতে শুরু করে বলে জানা যায়; বেশি মেমরি লাগে, এটিই এর দাম।
  • মোটা timer: সূক্ষ্ম সময় মাপার API-গুলোর (যেমন performance.now()) নির্ভুলতা কমানো হয়, আর কিছুটা এলোমেলোতা যোগ করা হয়। এটি মূল সমস্যা সারায় না, শুধু কঠিন করে — তাই একা যথেষ্ট নয়।
  • SharedArrayBuffer আর cross-origin isolation: একাধিক thread-এ ভাগ করা মেমরি দিয়ে নিজেই সূক্ষ্ম timer বানানো যায়, তাই ২০১৮ সালে প্রধান browser-গুলো এই সুবিধা সাময়িকভাবে বন্ধ করে। পরে এটি ফেরে একটি শর্তে: পাতাটিকে নির্দিষ্ট HTTP header (COOP আর COEP) দিয়ে নিজেকে “cross-origin isolated” ঘোষণা করতে হয়, যাতে অন্য origin-এর সম্পদ অনুমতি ছাড়া সেই process-এ ঢুকতে না পারে। খুঁটিনাটি browser-ভেদে আর সময়ের সাথে বদলেছে।

Microcode, firmware, OS আর নতুন hardware

অনেক প্রতিরক্ষা চালু করতে CPU-র নিজস্ব সহায়তা লাগে, যা আসে microcode update-এ (পর্ব ৩৪-এ দেখেছেন, x86-এর microcode update). এগুলো পৌঁছায় motherboard-এর firmware (BIOS/UEFI) update-এ, অথবা OS প্রতিবার চালু হওয়ার সময় লোড করে। পরের প্রজন্মের chip-গুলোতে নির্মাতারা কয়েকটি সমস্যা hardware-এই সারানোর কথা জানিয়েছেন, ফলে সেখানে software-প্রতিরক্ষার প্রয়োজন ও দাম কমে। কোন chip-এ কোনটি সারানো, তা নির্মাতার নিজস্ব নিরাপত্তা-নির্দেশিকায় দেখতে হয়।

গতির দাম

প্রতিটি স্তরের কিছু দাম আছে: KPTI kernel-প্রবেশে, retpoline প্রতিটি indirect লাফে, barrier যেখানে বসে সেখানে, site isolation মেমরিতে, predictor মোছা context switch-এর পরে নতুন করে শেখায়। কত দাম, তা workload, chip-এর প্রজন্ম আর OS-এর সংস্করণভেদে অনেক বদলায় — system call-ভারী আর I/O-ভারী কাজে প্রভাব বেশি চোখে পড়ে বলে জানা যায়। কোনো একটি শতাংশ সবার জন্য সত্য নয়; নিজের কাজের benchmark-ই নির্ভরযোগ্য উত্তর।

কোন সিস্টেমে কোন স্তরগুলো প্রাসঙ্গিক? নিচের সাহায্যকারী প্রোগ্রামটি কয়েকটি সাধারণ ধরনের সিস্টেমের জন্য একটি তালিকা ছাপে। এটি একটি শিক্ষামূলক চেকলিস্ট, নিরাপত্তা-নিরীক্ষা নয়:

// Defence-layer checklist: which layers matter for which kind of system.
// A teaching aid, not a security audit - always follow the vendor's own guidance.
const layers = [
  ["Keep the OS kernel updated (KPTI, branch-prediction controls)", p => p.os],
  ["Install firmware updates (BIOS/UEFI, CPU microcode if any)",   p => p.os || p.firmware],
  ["Keep the browser updated (site isolation, coarse timers)",     p => p.browser],
  ["Update the hypervisor; isolate tenants (SMT / core policy)",   p => p.tenants],
  ["Build with compiler mitigations where the vendor advises",     p => p.builds],
  ["Use vetted constant-time crypto libraries",                    p => p.crypto],
  ["Read the vendor advisory for this exact CPU or SoC",           p => true],
];
const systems = {
  "home laptop, browser user": { os: true, browser: true },
  "multi-tenant cloud host":   { os: true, firmware: true, tenants: true, crypto: true },
  "backend developer":         { os: true, browser: true, builds: true, crypto: true },
  "small embedded controller": { firmware: true },   // runs no untrusted code
};
for (const [name, p] of Object.entries(systems)) {
  const apply = layers.filter(([, rule]) => rule(p)).map(([text]) => text);
  console.log(`${name} -> ${apply.length} layer(s)`);
  apply.forEach(t => console.log("   - " + t));
}

আউটপুট (নির্ধারিত):

home laptop, browser user -> 4 layer(s)
   - Keep the OS kernel updated (KPTI, branch-prediction controls)
   - Install firmware updates (BIOS/UEFI, CPU microcode if any)
   - Keep the browser updated (site isolation, coarse timers)
   - Read the vendor advisory for this exact CPU or SoC
multi-tenant cloud host -> 5 layer(s)
   - Keep the OS kernel updated (KPTI, branch-prediction controls)
   - Install firmware updates (BIOS/UEFI, CPU microcode if any)
   - Update the hypervisor; isolate tenants (SMT / core policy)
   - Use vetted constant-time crypto libraries
   - Read the vendor advisory for this exact CPU or SoC
backend developer -> 6 layer(s)
   - Keep the OS kernel updated (KPTI, branch-prediction controls)
   - Install firmware updates (BIOS/UEFI, CPU microcode if any)
   - Keep the browser updated (site isolation, coarse timers)
   - Build with compiler mitigations where the vendor advises
   - Use vetted constant-time crypto libraries
   - Read the vendor advisory for this exact CPU or SoC
small embedded controller -> 2 layer(s)
   - Install firmware updates (BIOS/UEFI, CPU microcode if any)
   - Read the vendor advisory for this exact CPU or SoC

প্রতিটি সিস্টেমের বৈশিষ্ট্য (OS আছে কি না, browser, একাধিক গ্রাহক, নিজে কোড build করা, cryptography) থেকে নিয়মগুলো স্তর বেছে নেয়। বাড়ির ল্যাপটপে ৪টি স্তর — সাধারণ ব্যবহারকারীর জন্য এর মানে শুধু “OS, firmware আর browser update রাখা”। Cloud host-এ hypervisor আর গ্রাহকদের বিচ্ছিন্নতার স্তর যোগ হয়। ছোট embedded controller-এ মাত্র ২টি: যে যন্ত্র কোনো অচেনা কোড চালায় না আর যার core সরল in-order, সেখানে অনেক রূপ প্রাসঙ্গিকই নাও হতে পারে — তবে সেটিও নির্মাতার নির্দেশিকা দেখে নিশ্চিত হতে হয়, তাই শেষ লাইনটি প্রতিটি সিস্টেমে আছে।

৭ · Constant-time কোড — গোপন ডেটার ওপর শাখা বা index নয়

Spectre-এর আগেও cryptography-র জগতে একটি নিয়ম ছিল, যা এখন আরও জরুরি: গোপন ডেটা কখনো ঠিক করবে না কোন শাখা নেওয়া হবে, কোন মেমরি-ঠিকানা পড়া হবে, বা কোনো instruction কত সময় নেবে। এই নিয়মে লেখা কোডকে বলে constant-time code. নামটি একটু বিভ্রান্তিকর — এর মানে “সবসময় ঠিক একই nanosecond” নয়, বরং “সময় আর মেমরি-প্রবেশের ধরন গোপন মানের ওপর নির্ভর করে না”।

সবচেয়ে সাধারণ উদাহরণ দুটি byte-array-র তুলনা — যেমন একটি message authentication code (MAC) বা টোকেন মেলানো। সাধারণ তুলনা প্রথম অমিলেই থেমে যায়: প্রথম byte-ই না মিললে কাজ এক ধাপ, শেষ byte-এ অমিল হলে আট ধাপ। বাইরে থেকে কেউ সময়ের পার্থক্য দেখতে পেলে, তার অনুমান কতটা সঠিক ছিল সেই আভাস পায়। নিচের প্রোগ্রাম সময় মাপে না — শুধু লুপ কতবার ঘুরল তা গোনে, যাতে কাজের পরিমাণের পার্থক্যটি স্পষ্ট দেখা যায়:

// Compare a stored 8-byte tag with a guess in two ways. We COUNT loop steps (work done), not time.
function earlyExitEqual(a, b) {
  let steps = 0;
  for (let i = 0; i < a.length; i++) {
    steps++;
    if (a[i] !== b[i]) return { equal: false, steps };  // stops at the first mismatch
  }
  return { equal: true, steps };
}
function constantTimeEqual(a, b) {           // lengths are public and equal here
  let diff = 0, steps = 0;
  for (let i = 0; i < a.length; i++) {
    steps++;
    diff |= a[i] ^ b[i];                      // remember any difference, never stop early
  }
  return { equal: diff === 0, steps };
}
const stored = [0x3a, 0x91, 0x5c, 0x07, 0xe2, 0x48, 0xbd, 0x16];
const cases = [["mismatch at byte 0", 0], ["mismatch at byte 3", 3], ["mismatch at byte 7", 7], ["all 8 bytes equal", -1]];
console.log("guess".padEnd(20) + "early-exit".padStart(13) + "constant-time".padStart(15));
for (const [label, pos] of cases) {
  const guess = stored.slice();
  if (pos >= 0) guess[pos] ^= 0xff;           // flip one byte to create a mismatch
  const e = earlyExitEqual(stored, guess), c = constantTimeEqual(stored, guess);
  console.log(label.padEnd(20) + `${e.equal}, ${e.steps}`.padStart(13) + `${c.equal}, ${c.steps}`.padStart(15));
}
console.log("(each cell: result, loop steps)");

আউটপুট (নির্ধারিত):

guess                  early-exit  constant-time
mismatch at byte 0       false, 1       false, 8
mismatch at byte 3       false, 4       false, 8
mismatch at byte 7       false, 8       false, 8
all 8 bytes equal         true, 8        true, 8
(each cell: result, loop steps)

দুই function-এর ফল (false/true) প্রতিটি সারিতে একই — দুটিই সঠিক। পার্থক্য কাজের পরিমাণে: early-exit-এর ধাপ ১, ৪, ৮, ৮ — প্রথম অমিল কোথায়, তার সাথে সরাসরি বাঁধা। অর্থাৎ কাজের পরিমাণ গোপন তথ্যের (কতগুলো শুরুর byte মিলেছে) একটি আয়না। Constant-time রূপে প্রতিটি সারিতে ৮ ধাপ: সব byte-এর XOR জমা হয় diff-এ OR দিয়ে, কোনো শাখা ফলের ওপর নির্ভর করে না, শেষে শুধু একবার দেখা হয় diff শূন্য কি না। লক্ষ করুন, দৈর্ঘ্য এখানে প্রকাশ্য ও সমান ধরা হয়েছে। বাস্তবে JavaScript-এর মতো JIT-ভাষায় compiler কী করবে তার নিশ্চয়তা কম, তাই আসল কাজে platform-এর দেওয়া যাচাই করা function ব্যবহার করা উচিত (যেমন Node.js-এর crypto.timingSafeEqual, Python-এর hmac.compare_digest, OpenSSL-এর CRYPTO_memcmp).

Constant-time-এর তিনটি নিয়ম

নিয়মকেনসাধারণ উপায়
গোপন মানের ওপর শাখা নয়শাখার দিক সময় বদলায়, আর predictor-এর অবস্থাও বদলায়শাখার বদলে mask দিয়ে বাছাই: (m & a) | (~m & b)
গোপন মান দিয়ে মেমরির index নয়কোন cache লাইন এল, তা গোপন মানের আয়না হয় — অংশ ১-এর AES-টেবিলের শিক্ষাটেবিল এড়ানো, বা পুরো টেবিল পড়ে mask দিয়ে বাছাই
পরিবর্তনশীল-সময়ের instruction এড়ানোকিছু CPU-তে ভাগ বা কিছু গুণের সময় operand-এর ওপর নির্ভর করতে পারেনির্মাতার নথিতে “data-independent timing” তথ্য দেখা

নিজের crypto নিজে লিখবেন না। Constant-time কোড লেখা কঠিন, কারণ compiler optimization একটি সুন্দর শাখাহীন কোডকে আবার শাখাযুক্ত বানিয়ে ফেলতে পারে, আর নতুন hardware নতুন চিহ্ন তৈরি করতে পারে। ব্যাপকভাবে পর্যালোচিত, নিয়মিত হালনাগাদ হওয়া cryptography library (আপনার platform বা ভাষার মানসম্মত library) ব্যবহার করাই নিরাপদ পথ। Cryptography-র মৌলিক ধারণা দেখতে পারেন Computer Networks কোর্সের cryptography পর্বে।

৮ · বাস্তব করণীয় আর নকশার শিক্ষা — গতি বনাম নিরাপত্তা

কার কী করণীয়

আপনি যদি…প্রধান করণীয়
সাধারণ ব্যবহারকারীOS আর browser-এর স্বয়ংক্রিয় update চালু রাখা; ল্যাপটপ বা ডেস্কটপ নির্মাতার firmware (BIOS/UEFI) update দেখা; পুরোনো, update বন্ধ হয়ে যাওয়া OS বা browser এড়ানো
DeveloperCryptography-র জন্য যাচাই করা library আর তার constant-time তুলনার function; গোপন ডেটা আর অচেনা কোড একই process-এ না রাখা; compiler আর runtime হালনাগাদ রাখা; kernel বা sandbox-এর মতো সীমানার কোড লিখলে নির্মাতা ও প্রকল্পের নির্দেশিকা মানা
System administratorOS-এর নিরাপত্তা update আর microcode প্যাকেজ নিয়মিত বসানো; প্রতিরক্ষা কোনটি চালু তা দেখা (নিচের উদাহরণ); খরচ বাঁচাতে প্রতিরক্ষা বন্ধ করার আগে ঝুঁকি আর নির্মাতার নির্দেশিকা লিখিতভাবে বিবেচনা করা
Cloud বা virtualization চালানHypervisor হালনাগাদ রাখা; ভিন্ন গ্রাহকের virtual machine একই physical core-এর SMT thread-এ একসাথে না চালানোর নীতি বিবেচনা করা (পর্ব ৪৩, Hardware multithreading); নির্মাতার নির্দেশিকা অনুযায়ী hypervisor-স্তরের প্রতিরক্ষা চালু রাখা

Linux-এ kernel নিজেই জানায় কোন দুর্বলতার জন্য কোন প্রতিরক্ষা চালু আছে। নিচে কমান্ড আর আউটপুটের ধরন (আপনার machine-এ লাইনগুলো ভিন্ন হবে):

# উদাহরণ — Linux-এ প্রতিরক্ষার অবস্থা দেখা (শুধু পড়া, কিছু বদলায় না)
$ grep . /sys/devices/system/cpu/vulnerabilities/*
.../meltdown:Not affected
.../spectre_v1:Mitigation: ...
.../spectre_v2:Mitigation: ...
# প্রতিটি লাইনের তিন ধরনের উত্তর: "Not affected", "Mitigation: ..." বা "Vulnerable"

“Vulnerable” দেখলে প্রথম কাজ OS আর firmware হালনাগাদ করা, তারপর distribution-এর নিরাপত্তা-নির্দেশিকা পড়া। lscpu-এর আউটপুটেও একই তথ্য “Vulnerability” লাইনে দেখা যায়। OS-এর দিক থেকে নিরাপত্তা আর বিচ্ছিন্নতা নিয়ে আরও দেখুন OS কোর্সের নিরাপত্তা পর্বে আর sandboxing পর্বে।

Cloud-এ একাধিক গ্রাহক

Cloud-এ একটি physical server-এ অনেক গ্রাহকের virtual machine চলে, যারা cache, predictor, এমনকি SMT-তে একই core ভাগ করতে পারে। Linux-এ “core scheduling” নামে একটি সুবিধা আছে, যা একই core-এর sibling thread-এ শুধু পরস্পরকে বিশ্বাস করা কাজ চালাতে সাহায্য করে। গ্রাহক হিসেবে আপনার করণীয়: নিজের virtual machine-এর OS হালনাগাদ রাখা, আর অত্যন্ত সংবেদনশীল কাজের জন্য সেবাদাতার নিরাপত্তা-নথি পড়ে দেখা — যেমন একক-গ্রাহকের (dedicated) host-এর বিকল্প আছে কি না। বিস্তারিত ভাবনার প্রশ্ন ০৪-এ।

নকশার শিক্ষা — গতির প্রতিটি কৌশলের দাম

কয়েক দশক ধরে CPU দ্রুত হয়েছে অনুমান, ভাগাভাগি আর পুনর্ব্যবহারের মাধ্যমে: speculative execution, ভাগ করা cache আর predictor, SMT. প্রতিটিই এই অনুমানের ওপর দাঁড়িয়ে যে “ভেতরের অবস্থা ফলকে বদলায় না, তাই নিরাপত্তার প্রশ্নও নয়”। Spectre আর Meltdown দেখাল, ফল না বদলালেও সময় বদলায়, আর সময় তথ্য বহন করে। ফলে নকশাকারদের এখন গতির প্রতিটি কৌশলের সাথে জিজ্ঞেস করতে হয়: কে এই ভেতরের অবস্থা বদলাতে পারে, আর কে তার প্রভাব দেখতে পারে?

গবেষণা কয়েক দিকে সাড়া দিয়েছে। এক, নতুন hardware-নকশার প্রস্তাব — অনুমানের পথে আনা ডেটা আলাদা জায়গায় রাখা আর অনুমান নিশ্চিত হলে তবেই cache-এ বসানো, বা গোপন হতে পারে এমন মানের ওপর নির্ভরশীল ক্ষণস্থায়ী কাজ আটকে রাখা (২০১৮-১৯ সালের দিকের InvisiSpec, STT-র মতো প্রস্তাব)। দুই, hardware আর software-এর মধ্যে নতুন “চুক্তি” — ISA-র মতোই একটি আনুষ্ঠানিক বর্ণনা, কোন তথ্য সময়ের মাধ্যমে বেরোতে পারে আর কোনটি নয়। তিন, compiler আর বিশ্লেষণ টুল, যা ঝুঁকিপূর্ণ জায়গা খোঁজে বা constant-time নিয়ম যাচাই করে। আর software-এর দিকে একটি স্পষ্ট শিক্ষা: বিশ্বাসের সীমানা যেন process বা virtual machine-এর মতো hardware-রক্ষিত সীমানা হয়, একই address space-এর ভেতরে শুধু ভাষার নিয়ম নয়।

আরও গভীরে। আধুনিক out-of-order core-এ অনুমতি-যাচাই আর ডেটা পাঠানোর ক্রম এখন নকশার প্রথম দিনের সিদ্ধান্ত: load-এর ডেটা নির্ভরশীলদের কাছে পাঠানোর আগে অনুমতি নিশ্চিত করা, বা ব্যর্থ হলে শূন্য পাঠানো। Predictor-এর ঘরে privilege স্তর বা context-এর পরিচয় যোগ করা হয়, যাতে এক পক্ষের প্রশিক্ষণ অন্য পক্ষের অনুমানে না লাগে। Arm আর x86-এর নতুন সংস্করণে ISA-স্তরেও speculation-সংক্রান্ত বৈশিষ্ট্য যোগ হয়েছে, যাতে software জিজ্ঞেস করতে পারে chip কোন রূপ থেকে সুরক্ষিত। RISC-V-এর মতো খোলা ISA-তে এ নিয়ে আলোচনা আর প্রস্তাব চলছে বলে জানা যায় — খোলা নকশা এখানে গবেষণার সুবিধা দেয়, কারণ যে কেউ একটি out-of-order core-এর ভেতর পরীক্ষা করে দেখতে পারেন।

৯ · AI-এর যুগে — নিরাপত্তার প্রশ্নে AI, আর যাচাইয়ের অভ্যাস

এই বিষয়ে AI টুল ধারণা ব্যাখ্যায় ভালো, কিন্তু তিন জায়গায় সতর্ক থাকতে হয়: তারিখ ও নাম গুলিয়ে ফেলা, কোন chip আক্রান্ত তা আত্মবিশ্বাসের সাথে বানিয়ে বলা, আর প্রতিরক্ষার খরচের শতাংশ নির্দিষ্ট সংখ্যায় দাবি করা। আর একটি নিয়ম সবসময়: আক্রমণের কোড চাওয়া বা চালানো নয় — শেখার জন্য তার দরকার নেই।

AI-এর প্রচলিত ভুলঠিক কথাকীভাবে যাচাই
Spectre আর Meltdown-কে একই জিনিস বলাSpectre বৈধ কোড ভুল পথে চালায় (predictor); Meltdown কিছু নকশায় অনুমতি-যাচাই দেরিতে কার্যকর হওয়ার সুযোগ নেয়দুটির মূল কারণ আলাদা করে লিখে দেখা
“KPTI Spectre ঠেকায়” বলাKPTI মূলত Meltdown-এর বিরুদ্ধে; v1-এর জন্য barrier/masking, v2-এর জন্য retpoline ও predictor-নিয়ন্ত্রণপ্রতিটি প্রতিরক্ষা কোন ধাপটি ভাঙে, stepper মিলিয়ে দেখা
নির্দিষ্ট chip-এর তালিকা বা খরচের শতাংশ বানিয়ে বলানির্মাতার নিরাপত্তা-নির্দেশিকা আর নিজের benchmark-ই উৎসনির্মাতা বা OS distribution-এর প্রকাশিত নথি; নিজের workload-এ আগে-পরে মাপা
“Constant-time মানে একই nanosecond”সময় আর মেমরি-প্রবেশ গোপন মানের ওপর নির্ভর না করাকোডে গোপন মানের ওপর শাখা বা index আছে কি না খোঁজা
“Flush সব মুছে দেয়, তাই অনুমান নিরাপদ”Flush architectural state ফেরায়, microarchitectural নয়দুই স্তরের পার্থক্য (অংশ ১) মনে করা

AI-কে প্রশ্ন করার সময় প্রতিরক্ষার দিক আর অনিশ্চয়তা চিহ্নিত করার অনুরোধ স্পষ্ট রাখুন:

আমি কম্পিউটার আর্কিটেকচার শিখছি, আর Spectre/Meltdown শুধু ধারণা ও প্রতিরক্ষার দিক থেকে বুঝতে চাই। কোনো আক্রমণের বা সময় মাপার কোড দিও না। ১. Architectural আর microarchitectural state-এর পার্থক্য একটি টেবিলে দাও — প্রতিটির ৩টি উদাহরণসহ। ২. Spectre v1, Spectre v2 আর Meltdown — প্রতিটির মূল কারণ এক বাক্যে, আর প্রতিটির বিরুদ্ধে কোন প্রতিরক্ষা (KPTI, retpoline, barrier, index masking) কাজ করে, বলো। ৩. একটি সেকেন্ডে 60000 বার kernel-এ ঢোকা হয়, প্রতিবারে বাড়তি 300 ns ধরে নাও। খরচ কত শতাংশ? প্রতিটি গুণফল দেখাও। ৪. যে তারিখ, নাম বা chip-সংক্রান্ত তথ্যে তুমি নিশ্চিত নও, তা স্পষ্ট করে চিহ্নিত করো, আর কোন নথিতে মিলিয়ে দেখব তা বলো।

AI-এর উত্তর হাতে পেলে — যাচাইয়ের চেকলিস্ট

পরীক্ষা ও ইন্টারভিউতে এই বিষয়ে প্রায়ই যা জিজ্ঞেস করা হয়:

  • Architectural আর microarchitectural state-এর পার্থক্য কী? — প্রথমটি ISA-তে দৃশ্যমান (register, মেমরি) আর ভুল অনুমানে নিখুঁতভাবে ফেরে; দ্বিতীয়টি chip-এর ভেতরের (cache, predictor) আর ফেরানো হয় না।
  • Spectre আর Meltdown-এর মূল পার্থক্য? — Spectre predictor-কে ভুল পথে চালিয়ে বৈধ কোড দিয়ে ডেটা ছোঁয়; Meltdown কিছু নকশায় অনুমতি-যাচাই retire-এ কার্যকর হওয়ার সুযোগ নেয়।
  • KPTI কী আর এর দাম কোথায়? — user mode-এ kernel map না রাখা; প্রতিটি kernel-প্রবেশ-প্রস্থানে page table বদল, ASID/PCID থাকলে কম।
  • Retpoline কোন সমস্যার সমাধান? — Spectre v2: indirect লাফের অনুমানকে নিরীহ লুপে আটকে রাখে, BTB-র ভুল প্রশিক্ষণ কাজে লাগে না।
  • Constant-time তুলনা কীভাবে লিখবেন? — সব byte-এর XOR OR করে জমানো, শেষে একবার শূন্য-পরীক্ষা; আগেভাগে return নয় — বাস্তবে library-র function.

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।

প্র ০১Speculative execution মূলধারার CPU-তে ১৯৯০-এর দশক থেকে আছে। তাহলে এই সমস্যা প্রায় দুই দশক ধরে কেন কারও চোখে পড়েনি?

১৯৯৫ সালের Intel Pentium Pro-র মতো out-of-order, speculative core মূলধারায় আসার পর থেকেই অনুমানের পথে instruction চলেছে, cache-এ লাইন এসেছে, predictor শিখেছে। নকশাকারদের মানসিক মডেল ছিল পরিষ্কার: ISA একটি চুক্তি, আর চুক্তিতে শুধু ফলের কথা আছে। ভুল অনুমানের ফল কখনো commit হয় না, তাই প্রোগ্রামের দৃষ্টিতে কিছুই ঘটেনি। Cache বা predictor-এর অবস্থা “শুধু গতির বিষয়”, তাই নিরাপত্তার আলোচনায় তার জায়গা ছিল না।

অন্যদিকে নিরাপত্তা-গবেষকরা side channel ভালোই চিনতেন। ১৯৯৬ সালে Paul Kocher-এর timing attack-এর কাজ, ২০০৫ সালের দিকে cache-ভিত্তিক AES আক্রমণের গবেষণা দেখিয়েছিল, ভাগ করা cache তথ্য ফাঁস করতে পারে। কিন্তু সেই আক্রমণগুলোতে ভুক্তভোগী প্রোগ্রাম নিজেই, বৈধভাবে, গোপন ডেটা ছুঁয়ে চিহ্ন রাখত। Cryptography-র লোকেরা তাই constant-time নিয়ম বানালেন — নিজের কোডে গোপন-নির্ভর শাখা বা index না রাখা। অনুমানের পথে CPU নিজেই এমন কোড চালাতে পারে যা প্রোগ্রাম কখনো চালাতে চায়নি — এই দুই জগতের সংযোগটি কেউ পুরোপুরি জোড়া দেননি।

দুই জগতের মাঝে ছিল বিশেষজ্ঞতার দেয়াল। Microarchitecture-এর খুঁটিনাটি প্রায় সবসময় নির্মাতাদের গোপন তথ্য; বাইরের গবেষকরা পরীক্ষা করে আচরণ আন্দাজ করেন। আর chip-নকশাকারদের যাচাই-পদ্ধতি পরীক্ষা করত ফল সঠিক কি না, সময়ের মাধ্যমে তথ্য বেরোয় কি না তা নয়। ২০১৬-১৭ সালের দিকে কয়েকজন গবেষক বিভিন্ন দিক থেকে microarchitecture-এর ভেতরে খোঁজ শুরু করেন, আর ২০১৭ সালে একাধিক দল প্রায় একই সময়ে স্বাধীনভাবে একই উপসংহারে পৌঁছান — ধারণাটির সময় এসে গিয়েছিল। নির্মাতাদের সাথে কয়েক মাসের গোপন সমন্বয়ের পর ২০১৮ সালের জানুয়ারির শুরুতে প্রকাশ হয়, পরিকল্পিত তারিখের কিছুটা আগে বলে জানা যায়।

মূল উপলব্ধি: নিরাপত্তার ফাঁক প্রায়ই দুই বিশেষজ্ঞতার মাঝখানে থাকে। “এটি শুধু গতির বিষয়” — এমন যেকোনো অনুমান নিরাপত্তার দৃষ্টিতে পরীক্ষা করে দেখা উচিত, কারণ গতিও একটি পর্যবেক্ষণযোগ্য তথ্য।

প্র ০২সমস্যার মূল যদি speculative execution হয়, তাহলে সেটি পুরো বন্ধ করে দিলেই তো হয়। কেন কেউ তা করে না?

পর্ব ৩৭-এর হিসাব মনে করুন: গভীর pipeline-এ শাখার penalty ১০–২০ cycle, আর প্রায় প্রতি পাঁচটি instruction-এ একটি শাখা। অনুমান না করলে প্রতিটি শাখায় অপেক্ষা — CPI কয়েক গুণ বেড়ে যায়। Out-of-order core-এর পুরো লাভ (পর্ব ৪০) আসে শত খানেক instruction আগেভাগে চালানো থেকে, যার প্রায় সবই কোনো না কোনো অনুমানের পরে। Speculation বন্ধ মানে কার্যত একটি সরল in-order core-এর গতিতে ফেরা — বড় core-এর ক্ষেত্রে এটি কয়েক গুণ ধীরতা, কয়েক শতাংশ নয়।

বাস্তব বিকল্প আছে, আর তা ইতিমধ্যে আছে: সরল in-order core. অনেক ছোট core — microcontroller আর কিছু কম-শক্তির application core — খুব কম বা অগভীর অনুমান করে। ২০১৮ সালে কিছু নির্মাতা জানায় যে তাদের এমন কিছু ছোট core বেশিরভাগ রূপে আক্রান্ত নয়। কিন্তু সেগুলো ধীর, আর বড় server বা ল্যাপটপের চাহিদা মেটায় না। তাই শিল্প অন্য পথ নিয়েছে: অনুমান থাকবে, কিন্তু কোথায় ঝুঁকি, সেখানে বেছে বেছে থামানো — barrier শুধু ঝুঁকিপূর্ণ bounds check-এ, predictor মোছা শুধু বিশ্বাসের সীমানা পার হওয়ার সময়, KPTI শুধু আক্রান্ত chip-এ।

এর মধ্যে একটি গুরুত্বপূর্ণ ভাবনা আছে: নিরাপত্তা সব কোডের জন্য একই নয়। একটি ভিডিও encode করার লুপে কোনো গোপন তথ্য নেই, অন্য কারও কোডও সেখানে নেই — সেখানে পূর্ণ গতিতে অনুমান ঠিক আছে। কিন্তু kernel-এর system call-এর প্রবেশপথ, browser-এর JavaScript engine, hypervisor — এগুলো বিশ্বাসের সীমানা, যেখানে অচেনা পক্ষ ইনপুট দেয়। এই সীমানাগুলোতে খরচ দেওয়া যুক্তিসঙ্গত। গবেষণার নতুন hardware-প্রস্তাবগুলোও একই পথে: সব অনুমান বন্ধ নয়, শুধু “গোপন হতে পারে এমন মানের ওপর নির্ভরশীল ক্ষণস্থায়ী কাজ” আটকানো, যাতে গতির বেশিরভাগ রক্ষা পায়।

মূল উপলব্ধি: প্রতিরক্ষা “সব বা কিছুই না” নয়। কার্যকর নকশা ঝুঁকি যেখানে, শুধু সেখানে দাম দেয় — বিশ্বাসের সীমানা চিহ্নিত করা তাই নিরাপদ আর দ্রুত দুই রকম সিস্টেমেরই প্রথম ধাপ।

প্র ০৩Browser-এর sandbox বহু বছর ধরে নিরাপদ বলে ধরা হতো। Spectre-এর পর browser-এর নিরাপত্তা-চিন্তা কীভাবে বদলাল?

Spectre-এর আগে browser-এর নিরাপত্তার দুটি স্তর ছিল। ভেতরের স্তর ভাষা: JavaScript-এ pointer নেই, প্রতিটি array-প্রবেশ যাচাই হয়, তাই একটি পাতার কোড নিজের বাইরের মেমরি ছুঁতে পারে না। বাইরের স্তর process: পুরো browser-এর রেন্ডারিং অংশ একটি সীমিত-ক্ষমতার process-এ চলে, যাতে কোনো bug-এর সুযোগ নিলেও OS-এর ক্ষতি করা কঠিন হয়। কিন্তু অনেক সময় একাধিক website একই রেন্ডারিং process-এ চলত — ভাষার নিয়মই তাদের আলাদা রাখত।

Spectre v1 ঠিক এই ভেতরের স্তরকে আঘাত করে। ভাষার প্রতিটি bounds check একটি শাখা, আর অনুমানের পথে তা পার হওয়া যায়। JIT compiler যে দ্রুত machine code বানায়, তা এই নিয়মে সাধারণ C কোডের মতোই আচরণ করে। Browser engine-এর দলগুলো প্রথমে software-প্রতিরক্ষা চেষ্টা করে — index masking, কিছু জায়গায় barrier, সূক্ষ্ম timer মোটা করা, SharedArrayBuffer বন্ধ রাখা। ২০১৯ সালের দিকে একটি বড় browser engine দলের গবেষকরা প্রকাশ্যে বিশ্লেষণ করেন যে একই process-এর ভেতরে software দিয়ে সব রূপ নিশ্চিতভাবে ঠেকানো বাস্তবে সম্ভব নয় বলে মনে হয়; তাদের উপসংহার ছিল, বিচ্ছিন্নতা আসতে হবে process-এর সীমানা থেকে।

এর ফল site isolation: প্রতিটি site (মোটামুটি প্রতিটি মূল domain) নিজস্ব process-এ। তখন অনুমানের পথেও অন্য site-এর ডেটা address space-এ থাকে না — page table, অর্থাৎ hardware, সীমানা রক্ষা করে। দাম বেশি মেমরি আর জটিল browser-স্থাপত্য। সাথে এল cross-origin isolation-এর মডেল: যে পাতা SharedArrayBuffer-এর মতো শক্তিশালী সুবিধা চায়, তাকে HTTP header দিয়ে ঘোষণা করতে হয় যে সে অন্য origin-এর সম্পদ অনুমতি ছাড়া নিজের process-এ টেনে আনবে না।

মূল উপলব্ধি: software-এর নিয়ম দিয়ে তৈরি সীমানা hardware-এর অনুমানের কাছে দুর্বল হতে পারে। শক্ত নিরাপত্তা-সীমানা আসে hardware-রক্ষিত বিচ্ছিন্নতা থেকে — process, virtual machine — আর আধুনিক browser এখন সেভাবেই সাজানো।

প্র ০৪Cloud-এ একই physical server-এ বহু অপরিচিত গ্রাহক চলে। Speculation-এর সমস্যা cloud-এর ব্যবসায়িক মডেলের জন্য কেন বিশেষ প্রশ্ন তোলে?

Cloud-এর অর্থনীতি দাঁড়িয়ে আছে ভাগাভাগির ওপর: একটি শক্তিশালী server-এ অনেক গ্রাহকের virtual machine চলে, প্রত্যেকে মনে করেন যন্ত্রটি তাঁর একার। এই বিচ্ছিন্নতার প্রতিশ্রুতি দেয় hypervisor আর hardware-এর virtualization সুবিধা (পর্ব ৩৮-এ trap-and-emulate-এর ধারণা, আর OS কোর্সের virtual machine পর্ব)। কিন্তু একই chip-এ চলা মানে একই cache-এর কিছু স্তর, একই memory controller, আর SMT-তে একই core-এর predictor ও buffer ভাগ করা। Spectre-পরিবার দেখাল, এই ভাগাভাগি করা ভেতরের অবস্থা দিয়ে এক virtual machine অন্যটির — বা hypervisor-এর — তথ্যের আভাস পেতে পারে।

প্রতিরক্ষার দাম এখানে সরাসরি টাকার অঙ্কে আসে। Hypervisor-এ প্রবেশ-প্রস্থানে বাড়তি কাজ মানে প্রতি server-এ কম গ্রাহকের কাজ। SMT-র দুটি thread দুই গ্রাহককে না দেওয়ার নীতি নিলে core-এর ব্যবহারযোগ্য ক্ষমতা কমে। ২০১৮-র পর cloud-পরিচালনাকারীরা সাধারণত দ্রুত hypervisor আর host হালনাগাদ করেন, কখনো গ্রাহকদের virtual machine পুনরায় চালু করতে হয় — যা বড় পরিসরে একটি বিশাল কাজ। Linux-এ পরে “core scheduling” যোগ হয়, যা একই core-এর sibling thread-এ শুধু পরস্পর-বিশ্বস্ত কাজ রাখতে সাহায্য করে।

গ্রাহকের দিক থেকেও প্রশ্ন আসে: সংবেদনশীল কাজ — ব্যাংকের লেনদেন, স্বাস্থ্য-তথ্য, cryptographic key — কি ভাগ করা host-এ রাখা উচিত? অনেক সেবাদাতা একক-গ্রাহকের host-এর বিকল্প দেন, বেশি দামে। আরেকটি দিক হলো “confidential computing” — hardware-রক্ষিত বিচ্ছিন্ন পরিবেশ, যেখানে host-এর OS বা hypervisor-ও ডেটা পড়তে পারবে না; কিন্তু গবেষণায় দেখা গেছে, এমন পরিবেশও transient execution-এর কিছু রূপ থেকে মুক্ত ছিল না, তাই সেগুলোও নিয়মিত সংশোধন পায়।

মূল উপলব্ধি: ভাগাভাগি দক্ষতা আনে, আর দক্ষতার সাথে আসে পার্শ্ব-প্রভাব। Cloud-এ “কে কার সাথে কোন hardware ভাগ করছে” এখন একটি নিরাপত্তা-প্রশ্ন, যার উত্তর প্রযুক্তি আর ব্যবসা দুই দিক মিলিয়েই দিতে হয়।

প্র ০৫২০১৮-র পর কম্পিউটার আর্কিটেকচারের গবেষণা আর শিক্ষা কীভাবে বদলেছে? ভবিষ্যতের CPU-নকশাকার কী ভিন্নভাবে ভাববেন?

সবচেয়ে বড় বদল ধারণাগত: আগে CPU-র নিরাপত্তা মানে ছিল ISA-স্তরের প্রশ্ন — privilege mode, page table-এর অনুমতি, কোন instruction কে চালাতে পারে। এখন microarchitecture-ও নিরাপত্তার অংশ। আর্কিটেকচারের বড় সম্মেলনগুলোতে ২০১৮-র পর বহু প্রবন্ধ এসেছে transient execution-এর প্রতিরক্ষা নিয়ে, আর নিরাপত্তার সম্মেলনগুলোতে microarchitecture একটি নিয়মিত বিষয় হয়ে উঠেছে।

গবেষণা মোটামুটি তিন ধারায় এগিয়েছে। প্রথম, hardware-প্রস্তাব: অনুমানের পথে আনা ডেটা একটি আলাদা “ছায়া” জায়গায় রাখা আর অনুমান নিশ্চিত হলে তবেই cache-এ বসানো (InvisiSpec-ধাঁচের ধারণা), বা যে মান অনুমানের পথে পড়া হয়েছে তার ওপর নির্ভরশীল কাজকে নিশ্চয়তা না আসা পর্যন্ত আটকে রাখা (speculative taint tracking-ধাঁচের ধারণা)। দ্বিতীয়, আনুষ্ঠানিক মডেল: ISA যেমন ফলের চুক্তি, তেমনি “কোন তথ্য সময়ের মাধ্যমে বেরোতে পারে” তারও একটি চুক্তি লেখা, যাতে compiler আর hardware দুই পক্ষ জানে কার দায়িত্ব কী। তৃতীয়, টুল: compiler-এর বিশ্লেষণ, constant-time কোড যাচাইয়ের টুল, আর নকশার পর্যায়ে microarchitectural চিহ্ন খোঁজার পদ্ধতি।

শিল্পের অভ্যাসও বদলেছে। নির্মাতারা এখন নিয়মিত নিরাপত্তা-নির্দেশিকা প্রকাশ করেন, গবেষকদের সাথে সমন্বিত প্রকাশের (coordinated disclosure) প্রক্রিয়া আরও পরিণত হয়েছে, আর নতুন chip-এর ঘোষণায় কোন রূপ থেকে সুরক্ষিত তা উল্লেখ করা সাধারণ হয়ে উঠেছে।

শিক্ষার্থীর জন্য এর মানে: পাঠ্যবইয়ের pipeline, cache আর predictor শেখার সময় একটি নতুন প্রশ্ন জুড়ে নেওয়া — “এই কাঠামো কে বদলাতে পারে, আর কে তার প্রভাব দেখতে পারে?” ভবিষ্যতের নকশাকার প্রতিটি নতুন গতির কৌশলের সাথে তার নিরাপত্তা-বিশ্লেষণও লিখবেন, ঠিক যেমন আজ শক্তি-খরচের বিশ্লেষণ লেখা হয়। RISC-V-এর মতো খোলা ISA আর খোলা core-নকশা এখানে বিশেষ সুযোগ দেয়: বিশ্ববিদ্যালয়ের গবেষকরা — বাংলাদেশেও — একটি সম্পূর্ণ out-of-order core-এর ভেতর দেখে প্রতিরক্ষা পরীক্ষা করতে পারেন, যা বন্ধ নকশায় সম্ভব নয়।

মূল উপলব্ধি: গতি, শক্তি আর জায়গার পাশাপাশি নিরাপত্তা এখন CPU-নকশার চতুর্থ মাপকাঠি। Spectre আর Meltdown একটি যুগের সমাপ্তি চিহ্নিত করে — যে যুগে ধরে নেওয়া হতো ভেতরের অবস্থা কারও চোখে পড়ে না।

অনুশীলন

পরীক্ষার ধাঁচে লেখা — আগে খাতায় সমাধান করুন, তারপর উত্তর মিলিয়ে নিন।

  1. ঝুঁকিপূর্ণ ধাঁচ চিনুন: একটি kernel-এর তিনটি function-এর বর্ণনা, প্রতিটিতে i আসে user program-এর argument থেকে। (ক) i সীমার ভেতরে কি না যাচাই করে, তারপর names[i] পড়ে user-কে কপি করে দেয়। (খ) i যাচাই করে, তারপর a[i] পড়ে, আর সেই মানকে দ্বিতীয় একটি টেবিল b-এর index হিসেবে ব্যবহার করে। (গ) i নিয়ে কিছু পড়ে না, শুধু একটি counter-এ যোগ করে। কোনটি Spectre v1-এর ধাঁচে সবচেয়ে ঝুঁকিপূর্ণ, কেন, আর কী সংশোধন প্রস্তাব করবেন?
    • (খ) সবচেয়ে ঝুঁকিপূর্ণ। এতে v1-এর দুটি শর্তই আছে: বাইরের পক্ষ index ঠিক করে, আর অনুমানের পথে সীমার বাইরে পড়া মান আরেকটি মেমরি-প্রবেশের ঠিকানা ঠিক করে — ফলে কোন cache লাইন আসবে, তা গোপন মানের ওপর নির্ভর করে।
    • (ক)-তে অনুমানের পথে সীমার বাইরের একটি মান পড়া হতে পারে, কিন্তু তা দিয়ে আর কোনো ঠিকানা বানানো হয় না; কপির কাজ commit-এর আগে architectural ফল দেয় না। তবু এটি নিরাপদ বলে নিশ্চিত হওয়া যায় না (পরের কোড কী করে তার ওপর নির্ভর করে), তাই পর্যালোচনায় চিহ্নিত রাখা উচিত। (গ)-তে মেমরি-প্রবেশই নেই — ঝুঁকি নেই।
    • সংশোধন: (খ)-তে যাচাইয়ের ঠিক পরে index masking (Linux-এ array_index_nospec()-এর ধাঁচ), যাতে অনুমানের পথেও i সীমার ভেতরে থাকে; অথবা যাচাইয়ের পরে একটি speculation barrier. Masking সাধারণত সস্তা, তাই প্রথম পছন্দ; একাধিক load-এ ছড়িয়ে থাকা জটিল কোডে barrier সহজ।
  2. Barrier কেন কাজ করে: নিজের ভাষায় ব্যাখ্যা করুন — bounds check-এর পরে একটি speculation barrier বসালে Spectre v1-এর কোন ধাপটি আর ঘটে না, আর কেন barrier-টি সব শাখার পরে বসানো হয় না। একটি বাক্যে বলুন index masking কেন barrier-এর চেয়ে সস্তা।
    • Barrier নির্দেশ দেয়: আগের instruction-গুলো (এখানে bounds check-এর তুলনা) নিষ্পত্তি না হওয়া পর্যন্ত পরের load শুরু হবে না। ফলে stepper-এর ধাপ ৩ — ক্ষণস্থায়ী কাজ — আর ঘটে না: শর্ত মিথ্যা হলে ভেতরের load কখনো চলেই না, তাই গোপন মানের ওপর নির্ভরশীল cache-চিহ্নও তৈরি হয় না। ধাপ ১–২ (প্রশিক্ষণ, ভুল অনুমান) ঘটতে পারে, কিন্তু তা ক্ষতিহীন।
    • সব শাখার পরে নয়, কারণ barrier সেই জায়গায় out-of-order আর অনুমানের লাভ মুছে দেয় — প্রতিটি শাখায় বসালে CPU কার্যত একটি ধীর in-order core হয়ে যাবে (ভাবনার প্রশ্ন ০২)। শুধু যেখানে বাইরের পক্ষ index ঠিক করে আর পড়া মান পরে ঠিকানা হয়, সেখানে বসানো হয়।
    • Index masking-এ CPU থামে না; কয়েকটি সরল গাণিতিক instruction data dependency দিয়ে index-কে সীমার ভেতরে আনে, আর বাকি সব অনুমান স্বাভাবিকভাবে চলে।
  3. Constant-time রূপে লিখুন: নিচের function একটি ৬ অঙ্কের PIN (দুটি সমান দৈর্ঘ্যের array) মেলায়। এটিকে constant-time রূপে লিখুন, আর বলুন কেন বাস্তবে নিজের লেখা রূপের বদলে library-র function ব্যবহার করা উচিত।
    function checkPin(entered, stored) {
      for (let i = 0; i < stored.length; i++)
        if (entered[i] !== stored[i]) return false;
      return true;
    }
    // উদাহরণ — constant-time রূপ (দৈর্ঘ্য প্রকাশ্য ও সমান ধরা)
    function checkPinCT(entered, stored) {
      if (entered.length !== stored.length) return false;  // length is public, not secret
      let diff = 0;
      for (let i = 0; i < stored.length; i++)
        diff |= entered[i] ^ stored[i];                     // no early exit
      return diff === 0;
    }
    • প্রতিটি ডাকে লুপ ঠিক stored.length বার ঘোরে; কোনো শাখা গোপন অঙ্কের ওপর নির্ভর করে না। অংশ ৭-এর ধাপ-গোনা প্রোগ্রামের মতো গুনলে প্রতিটি ইনপুটে একই ধাপ পাওয়া যাবে।
    • দৈর্ঘ্যের পরীক্ষা আগেভাগে return করে, কিন্তু দৈর্ঘ্য এখানে গোপন নয় (PIN সবসময় ৬ অঙ্ক)।
    • Library কেন: JIT বা compiler optimization শাখাহীন কোডকে আবার শাখাযুক্ত করতে পারে, আর ভাষাভেদে সংখ্যার ধরনেও ফাঁদ আছে। Platform-এর যাচাই করা function (যেমন Node.js-এ crypto.timingSafeEqual) এসব পরীক্ষিত। আর বাস্তবে PIN বা password সরাসরি রাখা হয় না — তার নিরাপদ hash রাখা হয়, আর সেটিও library দিয়ে মেলানো হয়।
  4. KPTI-র খরচ হিসাব (অনুমিত মান): একটি server-এর প্রতিটি core সেকেন্ডে গড়ে ৮০,০০০ বার kernel-এ ঢোকে-বেরোয়। ধরে নিন KPTI-র কারণে প্রতিটি প্রবেশ-প্রস্থানে বাড়তি ০.৪ µs লাগে (ASID ছাড়া) আর ০.১ µs (ASID-সহ)। (ক) দুই ক্ষেত্রে overhead কত শতাংশ? (খ) ASID ছাড়া overhead ২% ছাড়াবে না, এমন সর্বোচ্চ প্রবেশের হার কত? (গ) একটি application প্রতিটি ছোট লেখার জন্য আলাদা system call না করে কয়েকটি একসাথে জমিয়ে পাঠাল, ফলে হার ৪ ভাগের ১ হলো। ASID ছাড়া নতুন overhead কত?
    • (ক) ASID ছাড়া: 80,000 × 0.4 × 10−6 s = 0.032 s প্রতি সেকেন্ডে → ৩.২%. ASID-সহ: 80,000 × 0.1 × 10−6 = 0.008 s → ০.৮%.
    • (খ) 0.02 / (0.4 × 10−6) = ৫০,০০০ প্রবেশ প্রতি সেকেন্ডে.
    • (গ) নতুন হার 20,000; 20,000 × 0.4 × 10−6 = 0.008 → ০.৮%. শিক্ষা: system call-এর সংখ্যা কমানো (batching) প্রতিরক্ষা বন্ধ না করেই খরচ কমায় — কারণ খরচ হারের সাথে রৈখিক। সব সংখ্যাই অনুমিত; আসল সিদ্ধান্তের আগে নিজের workload-এ মাপতে হয়।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমKPTI চালু থাকা একটি সিস্টেমে একটি system call-এর ঘটনাগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. User mode-এ প্রোগ্রাম চলছে user page table নিয়ে, যাতে kernel-এর প্রায় কিছুই map করা নেই
  2. System call instruction CPU-কে supervisor mode-এ entry stub-এ নিয়ে যায়
  3. Entry stub page table-এর ভিত্তি-register বদলে kernel page table বসায়
  4. Kernel অনুরোধের আসল কাজ করে
  5. ফেরার আগে আবার user page table বসানো হয়, তারপর user mode-এ ফেরা

একটি out-of-order CPU ভুল অনুমানের পথের instruction-গুলো ফেলে দিল (flush). নিচের কোনটি তারপরও ভুল পথের প্রভাব ধরে রাখতে পারে?

  • General-purpose register-এর মান
  • মেমরিতে লেখা ডেটা
  • Cache-এ কোন লাইন আছে
  • Program counter (PC)

KPTI (kernel page-table isolation) প্রধানত কোন সমস্যার বিরুদ্ধে software-প্রতিরক্ষা?

  • Spectre v1 (bounds check bypass)
  • Meltdown
  • Spectre v2 (branch target injection)
  • Cryptography-র কোডে গোপন-নির্ভর শাখা

একটি kernel function user program থেকে আসা index i সীমার ভেতরে কি না যাচাই করে, তারপর a[i] পড়ে সেই মানকে দ্বিতীয় একটি টেবিল b-এর index হিসেবে ব্যবহার করে। নির্দিষ্ট এই জায়গায় Spectre v1 ঠেকানোর সবচেয়ে উপযুক্ত সংশোধন কোনটি?

  • KPTI চালু করা
  • Retpoline দিয়ে compile করা
  • আরেকটি if দিয়ে i আবার যাচাই করা
  • যাচাইয়ের পরে index masking বা speculation barrier

Retpoline কোন ধরনের instruction-কে লক্ষ্য করে বানানো compiler-কৌশল?

  • Register থেকে গন্তব্য নেওয়া indirect jump আর call
  • Conditional branch (beq, bne)
  • মেমরির load আর store
  • গুণ আর ভাগের instruction

দুটি ১৬ byte-এর MAC মেলানোর জন্য কোন পদ্ধতি constant-time?

  • প্রথম অমিল পেলেই false ফেরত দেওয়া লুপ
  • C-এর সাধারণ memcmp
  • সব byte-এর XOR OR করে জমিয়ে শেষে একবার শূন্য কি না দেখা
  • দুটি array sort করে তারপর তুলনা

একটি core সেকেন্ডে ৫০,০০০ বার kernel-এ ঢোকে-বেরোয়, আর প্রতিবার বাড়তি ০.৫ µs লাগে বলে ধরে নেওয়া হলো। অনুমিত overhead কত?

  • ০.২৫%
  • ২.৫%
  • ২৫%
  • ৫০%

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. দুই স্তর: ভুল অনুমানে architectural state (register, মেমরি, PC) নিখুঁতভাবে ফেরে; microarchitectural state (cache, predictor, TLB) ফেরে না। ISA ফলের চুক্তি, সময়ের নয় — সময়ের পার্থক্যই side channel.
  2. Spectre: v1 — bounds check-এর শাখা অনুমানে পার হয়ে সীমার বাইরের মান আরেকটি ঠিকানা ঠিক করে; v2 — ভাগ করা indirect predictor-কে প্রশিক্ষণ দিয়ে অন্যের লাফ ভুল গন্তব্যে অনুমান করানো।
  3. Meltdown: কিছু নকশায় অনুমতি-যাচাই retire-এ কার্যকর হয়, ডেটা তার আগেই নির্ভরশীলদের কাছে পৌঁছায়। প্রতিরক্ষা: KPTI (user mode-এ kernel map না রাখা) আর নতুন hardware-এ আগেই আটকানো।
  4. প্রতিরক্ষার মানচিত্র: v1 → barrier বা index masking; v2 → retpoline আর predictor-নিয়ন্ত্রণ; browser → site isolation, মোটা timer, cross-origin isolation; সবার জন্য → microcode, firmware, OS আর browser update. প্রতিটির দাম workload-ভেদে, নিজে মাপতে হয়।
  5. Constant-time: গোপন মানের ওপর শাখা, মেমরির index বা পরিবর্তনশীল-সময়ের instruction নয়; বাস্তবে যাচাই করা crypto library আর তার তুলনার function.

পরের পর্বে: একটি instruction দিয়ে একসাথে অনেক ডেটা — SIMD আর vector. SSE, AVX থেকে RISC-V Vector extension, আর কেন ছবি, অডিও আর AI-এর হিসাবে এগুলো এত কাজের।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান