บทความที่กล่าวถึงโดย sgbj ในความคิดเห็นที่เขียนโดย Paul Turner ของ Google จะอธิบายรายละเอียดต่อไปนี้ แต่ฉันจะให้มัน:
เท่าที่ฉันสามารถรวมข้อมูลจากข้อมูลที่ จำกัด ในตอนนี้ retpoline เป็นแทรมโพลีนกลับที่ใช้วนวนไม่สิ้นสุดที่ไม่เคยดำเนินการเพื่อป้องกัน CPU จากการเก็งกำไรในเป้าหมายของการกระโดดทางอ้อม
วิธีการพื้นฐานสามารถดูได้ในสาขาเคอร์เนลของ Andi Kleenเพื่อจัดการกับปัญหานี้:
แนะนำการ__x86.indirect_thunk
โทรใหม่ที่โหลดเป้าหมายการโทรที่มีที่อยู่หน่วยความจำ (ซึ่งฉันจะโทรADDR
) จะถูกเก็บไว้ที่ด้านบนของสแต็กและดำเนินการกระโดดโดยใช้RET
คำสั่ง thunk นั้นถูกเรียกโดยใช้แมโค NOSPEC_JMP / CALLซึ่งถูกใช้เพื่อแทนที่การโทรและข้ามทางอ้อมจำนวนมาก (ถ้าไม่ใช่ทั้งหมด) แมโครจะวางเป้าหมายการโทรไว้บนสแต็กและตั้งค่าที่อยู่ผู้ส่งคืนอย่างถูกต้องหากจำเป็น (หมายเหตุการควบคุมการไหลที่ไม่ใช่เชิงเส้น):
.macro NOSPEC_CALL target
jmp 1221f /* jumps to the end of the macro */
1222:
push \target /* pushes ADDR to the stack */
jmp __x86.indirect_thunk /* executes the indirect jump */
1221:
call 1222b /* pushes the return address to the stack */
.endm
การวางตำแหน่งcall
ในสุดจำเป็นเพื่อให้เมื่อการโทรทางอ้อมเสร็จสิ้นโฟลว์การควบคุมยังคงดำเนินต่อไปหลังการใช้NOSPEC_CALL
แมโครดังนั้นจึงสามารถใช้งานได้ในตำแหน่งปกติcall
ตัวตนมีลักษณะดังต่อไปนี้:
call retpoline_call_target
2:
lfence /* stop speculation */
jmp 2b
retpoline_call_target:
lea 8(%rsp), %rsp
ret
โฟลว์การควบคุมอาจทำให้สับสนเล็กน้อยที่นี่ดังนั้นให้ฉันอธิบาย:
call
ดันตัวชี้คำสั่งปัจจุบัน (เลเบล 2) ไปยังสแต็ก
lea
เพิ่ม 8 ให้กับสแต็คพอยน์เตอร์ซึ่งละทิ้ง quadword ที่พุชล่าสุดซึ่งเป็นที่อยู่ผู้ส่งคืนล่าสุด (ไปยังเลเบล 2) ได้อย่างมีประสิทธิภาพ หลังจากนี้จุดสูงสุดของสแต็กจะชี้ไปที่ที่อยู่ผู้ส่งที่แท้จริงของ ADDR อีกครั้ง
ret
ข้ามไป*ADDR
และรีเซ็ตตัวชี้สแต็กเป็นจุดเริ่มต้นของสแตกการโทร
*ADDR
ในท้ายที่สุดพฤติกรรมทั้งหมดนี้เป็นจริงเทียบเท่ากับการกระโดดโดยตรงกับ ประโยชน์อย่างหนึ่งที่เราได้รับคือตัวทำนายสาขาที่ใช้สำหรับคำสั่งส่งคืน (Return Stack Buffer, RSB) เมื่อดำเนินการcall
คำสั่งสมมติว่าret
คำสั่งที่เกี่ยวข้องจะข้ามไปที่ป้ายกำกับ 2
ส่วนหลังจากฉลาก 2 ไม่เคยถูกประหารจริงมันเป็นเพียงแค่วงวนไม่สิ้นสุดที่ตามทฤษฎีแล้วจะเติมคำJMP
แนะนำ โดยการใช้LFENCE
งานPAUSE
หรือมากกว่านั้นคำสั่งที่ทำให้ท่อส่งคำสั่งหยุดการทำงานของ CPU ไม่ให้สิ้นเปลืองพลังงานและเวลาในการดำเนินการเก็งกำไร นี่เป็นเพราะในกรณีที่การเรียกไปที่ retpoline_call_target จะกลับมาตามปกติLFENCE
จะเป็นคำสั่งต่อไปที่จะดำเนินการ นี่คือสิ่งที่ตัวพยากรณ์สาขาจะทำนายตามที่อยู่ผู้ส่งคืนเดิม (ฉลาก 2)
อ้างจากคู่มือสถาปัตยกรรมของ Intel:
คำแนะนำต่อไปนี้อาจถูกดึงมาจากหน่วยความจำ LFENCE ก่อน LFENCE แต่จะไม่ทำงานจนกว่า LFENCE จะเสร็จสิ้น
อย่างไรก็ตามโปรดทราบว่าสเปคไม่เคยพูดถึงว่า LFENCE และหยุดชั่วคราวทำให้ท่อส่งไปยังแผงลอยดังนั้นฉันอ่านบิตระหว่างบรรทัดที่นี่
ตอนนี้กลับไปที่คำถามเดิมของคุณ: การเปิดเผยข้อมูลหน่วยความจำเคอร์เนลเป็นไปได้เนื่องจากการรวมสองแนวคิด:
แม้ว่าการดำเนินการเก็งกำไรควรจะเป็นผลข้างเคียงได้ฟรีเมื่อการเก็งกำไรผิดดำเนินการเก็งกำไรยังคงส่งผลกระทบต่อลำดับชั้นแคช ซึ่งหมายความว่าเมื่อทำการโหลดหน่วยความจำแบบพิเศษมันอาจยังคงทำให้เกิดบรรทัดแคช การเปลี่ยนแปลงในลำดับชั้นของแคชสามารถระบุได้โดยการวัดเวลาในการเข้าถึงหน่วยความจำที่ถูกแมปเข้ากับชุดแคชเดียวกันอย่างระมัดระวัง
คุณสามารถรั่วไหลของหน่วยความจำบางส่วนเมื่อแหล่งที่มาของการอ่านหน่วยความจำถูกอ่านจากหน่วยความจำเคอร์เนล
ตัวพยากรณ์สาขาทางอ้อมของ CPU ของ Intel ใช้เฉพาะคำสั่งแหล่งกำเนิดต่ำสุด 12 บิตเท่านั้นดังนั้นจึงเป็นเรื่องง่ายที่จะทำให้พิษการทำนายที่เป็นไปได้ทั้งหมด 2 ^ 12 ด้วยที่อยู่หน่วยความจำที่ผู้ใช้ควบคุม สิ่งเหล่านี้สามารถเมื่อคาดการณ์การกระโดดทางอ้อมภายในเคอร์เนลจะดำเนินการอย่างพิเศษด้วยสิทธิ์เคอร์เนล การใช้ช่องสัญญาณด้านเวลาแคชคุณสามารถรั่วไหลหน่วยความจำเคอร์เนลโดยพลการ
อัปเดต: ในรายชื่อส่งเมลเคอร์เนลมีการสนทนาอย่างต่อเนื่องที่ทำให้ฉันเชื่อว่าเรโทรโพลิสไม่ได้ลดปัญหาการคาดคะเนสาขาอย่างเต็มที่เช่นเมื่อ Return Stack Buffer (RSB) หมดเปล่าสถาปัตยกรรม Intel ล่าสุด (Skylake +) ไปยัง Branch Target Buffer (BTB):
Retpoline ในฐานะกลยุทธ์บรรเทาผลกระทบจะแลกเปลี่ยนกิ่งไม้ทางอ้อมเพื่อรับผลตอบแทนเพื่อหลีกเลี่ยงการใช้การคาดการณ์ที่มาจาก BTB เนื่องจากอาจถูกวางยาพิษโดยผู้โจมตี ปัญหาของ Skylake + คือว่าอันเดอร์โฟล RSB กลับไปใช้การคาดคะเน BTB ซึ่งทำให้ผู้โจมตีสามารถควบคุมการเก็งกำไรได้