หลัง Linux 6.9 ฟังก์ชัน LUKS suspend ไม่สามารถลบคีย์เข้ารหัสดิสก์ออกจากหน่วยความจำได้
(mathstodon.xyz)- ตั้งแต่ Linux 6.9 เป็นต้นมา เครื่องมือที่ใช้ล็อกไดรฟ์เมื่อโน้ตบุ๊กเข้าสู่โหมด suspend ได้ ล้มเหลวแบบเงียบ ๆ ทำให้คีย์เข้ารหัสแบบ full-disk ของ LUKS ยังคงค้างอยู่ในหน่วยความจำ
- สาเหตุมาจาก การรีแฟกเตอร์การเข้าถึง block device ที่เข้าใน Linux 6.9 เมื่อเดือนพฤษภาคม 2024 และปฏิสัมพันธ์ที่ไม่คาดคิดกับโค้ดเข้ารหัส โดยวิธีแก้ที่เสนอเป็นแพตช์เพียงบรรทัดเดียว
- ปัญหานี้ไม่ปรากฏในกรณีปิดเครื่องทั้งหมด แต่ใน suspend-to-RAM คีย์ยังคงอยู่ ทำให้ผู้โจมตีที่ยึดโน้ตบุ๊กซึ่งยังเปิดเครื่องอยู่สามารถ ดึงคีย์ออกจาก RAM ได้
- การค้นพบเริ่มจากการตรวจสอบพอร์ต NixOS ของ Debian
cryptsetup-suspendแล้วพบรายการใน/proc/keysก่อนจะยืนยันด้วย memory dump จาก QEMU ว่า volume key ที่ควรถูกลบยังคงอยู่ - มีการเสนอทั้งการทดสอบรวมของ NixOS และแพตช์เตือนใน cryptsetup โดยชี้ว่าฟีเจอร์ความปลอดภัยอย่างการลบคีย์ก่อน suspend แม้จะดูเหมือนทำงานปกติ แต่หากไม่มี การตรวจสอบหน่วยความจำจริง ก็อาจพลาดความล้มเหลวได้ง่าย
ปัญหาที่คีย์ LUKS ยังคงอยู่ระหว่าง suspend หลัง Linux 6.9
- ตั้งแต่ Linux 6.9 หรือเดือนพฤษภาคม 2024 เป็นต้นมา เครื่องมือที่ใช้ล็อกไดรฟ์เมื่อโน้ตบุ๊กเข้าสู่โหมด suspend ได้ ล้มเหลวแบบเงียบ ๆ
- การเข้ารหัสดิสก์ทั้งลูกด้วย LUKS ถูกใช้เพื่อปกป้องข้อมูลเมื่อโน้ตบุ๊กสูญหาย ถูกยึด หรือถูกขโมย แต่ในกรณีนี้คีย์เข้ารหัสยังคงอยู่ในหน่วยความจำระหว่าง suspend
- แม้ยังทำงานได้เมื่อปิดเครื่องทั้งหมด แต่ผลกระทบมีมากเพราะผู้ใช้จำนวนมากมักปล่อยเครื่องไว้ในโหมด suspend-to-RAM มากกว่าปิดเครื่องสนิท
- หากมีผู้ใดเข้าครอบครองโน้ตบุ๊กขณะที่เครื่องยังมีไฟอยู่ ก็จะอยู่ในสถานะที่คีย์ซึ่งค้างในหน่วยความจำอาจรั่วไหลได้
- มีการกล่าวถึง VeraCrypt ใน Windows ว่าเป็นซอฟต์แวร์เพื่อวัตถุประสงค์เดียวกัน แต่ต่อมาในคอมเมนต์มีการแก้ไขว่าคำว่า “canonical software” หมายถึงซอฟต์แวร์ที่เป็นตัวแทนแนะนำในสาย IT security ไม่ใช่ซอฟต์แวร์ที่ถูกใช้มากที่สุด
สาเหตุและแพตช์บรรทัดเดียว
- สาเหตุมาจากคอมมิตรีแฟกเตอร์ใน Linux kernel คือ md: port block device access to file
- ตัวการเปลี่ยนแปลงเองเป็นการรีแฟกเตอร์ที่สมเหตุสมผลและมีประโยชน์ แต่ก่อให้เกิด ปฏิสัมพันธ์ระยะไกล กับโค้ดเข้ารหัส
- วิธีแก้ที่เสนอคือ แพตช์บรรทัดเดียว
- ผู้เขียนแพตช์ระบุว่า หากไม่มีการตรวจสอบเชิงรูปแบบ ก็ไม่อาจยืนยันได้ว่าแพตช์นี้ถูกต้องและไม่มีปฏิสัมพันธ์ระยะไกลอื่น ๆ
- มีงานต่อเนื่องเพื่อป้องกันการเกิดซ้ำด้วย
- การทดสอบอัตโนมัติของ NixOS: การทดสอบรวมเพื่อจับ regression ในอนาคต
- merge request ของ cryptsetup: แพตช์ให้ แสดงคำเตือน แทนที่จะล้มเหลวแบบเงียบ ๆ
กระบวนการค้นพบ
- จุดเริ่มต้นคือการเก็บกวาดพอร์ต NixOS ของ Debian
cryptsetup-suspend - ทั้งต้นฉบับของ Debian และพอร์ตของ NixOS ต่างมี race condition ที่สร้างความรำคาญแต่ไม่เป็นอันตราย ซึ่งบางครั้งทำให้โน้ตบุ๊กไม่สามารถเข้าสู่โหมดหลับได้
- เพื่อแก้ปัญหานี้ จึงพยายามชุบชีวิตแพตช์เคอร์เนลที่ยังไม่ถูกรวมของ Pali Rohár คือ แพตช์ลบคีย์สำหรับ dm-crypt suspend/hibernation
- ระหว่างนั้น เมื่อไล่ดูซอร์สโค้ดของ cryptsetup และเคอร์เนล ก็พบว่าตามเอกสาร keyring จะผูกกับ thread ที่เรียกใช้ และจะถูกลบเมื่อ thread นั้นจบการทำงาน
- แต่กลับเห็นรายการใน
/proc/keysซึ่งก่อนหน้านี้ไม่เคยทราบมาก่อน ทำให้เริ่มสงสัยมากขึ้น - ท้ายที่สุดจึงรันเครื่องเสมือน QEMU และ dump หน่วยความจำ จนยืนยันได้ว่า LUKS volume key ที่ควรถูกลบยังคงค้างอยู่
โปรเจกต์ secure suspend-to-RAM ของ NixOS
- โปรเจกต์
secure-suspendที่เผยแพร่ออกมาแยกต่างหาก มอบฟังก์ชัน secure suspend-to-RAM แบบทดลองสำหรับ NixOS - การเข้ารหัสดิสก์ทั้งลูกทั่วไปมักทำให้คีย์ยังคงอยู่ในหน่วยความจำเมื่อโน้ตบุ๊กอยู่ในสถานะ suspend จึงอาจเสี่ยงต่อ cold boot attack หรือการรั่วไหลผ่าน RAM
- โปรเจกต์นี้ใช้แนวทางชุบชีวิตแพตช์เคอร์เนลเก่าของ Pali Rohár เพื่อให้ลบคีย์เข้ารหัส LUKS เมื่อ suspend
- แม้ได้แรงบันดาลใจจาก Debian
cryptsetup-suspendแต่ใช้แพตช์เคอร์เนลเพื่อหลีกเลี่ยง race condition ที่บางครั้งทำให้โน้ตบุ๊กไม่ยอมหลับ และยังเพิ่มมาตรการป้องกันเพิ่มเติม - รองรับ encrypted root filesystem อย่างสมบูรณ์ และมีการทดสอบรวมให้ด้วย
- ทั้งแพตช์เคอร์เนลและเครื่องมือฝั่ง user space สามารถปรับใช้กับ Linux distribution อื่นได้เช่นกัน
- โปรเจกต์เผยแพร่อยู่ที่ secure-suspend
เหตุใดการตรวจสอบความปลอดภัยของ suspend จึงยาก
- การเห็นหน้าจอล็อกหลัง suspend ไม่ได้หมายความว่าอุปกรณ์เก็บข้อมูลถูกล็อกจริง
- หากหลังปลุกจาก suspend แล้วยังเข้าถึงดิสก์ได้ทันที ก็แปลว่าอุปกรณ์เก็บข้อมูลไม่เคยถูกล็อกตั้งแต่แรก
- ตัวอย่างเช่น สามารถใช้สคริปต์ที่ยังรันต่ออยู่หลังหน้าจอล็อกเพื่อตรวจสอบการเข้าถึงดิสก์ได้
- หากหลัง suspend ยังเชื่อมต่อด้วย SSH public key ได้โดยไม่ต้องปลดล็อกสตอเรจเข้ารหัสก่อน ก็ยืนยันได้ง่ายว่าอุปกรณ์เก็บข้อมูลไม่ได้ถูกล็อก
- ยังมีคอมเมนต์ด้วยว่า การตั้งค่าเริ่มต้นของ Ubuntu หรือ Debian ไม่ได้พยายามให้การป้องกันแบบนี้เลย
- การพยายามล็อกอุปกรณ์เก็บข้อมูลจะทำงานถูกต้องจริงหรือไม่ จำเป็นต้องตรวจสอบแยกต่างหาก
- timestamp ใน log อาจถูกสร้างก่อน suspend แต่เพิ่งถูกเขียนลงจริงหลัง wake
- ในทางกลับกัน log ที่ถูกสร้างทันทีหลัง wake ก่อนที่เวลาระบบจะถูกปรับ ก็อาจดูเหมือนเป็นเวลาตอน suspend ได้
- การที่การล็อกสตอเรจเกิดขึ้นก่อน suspend ทันที หรือเพิ่งเกิดหลัง resume ทันที แม้ประสบการณ์ผู้ใช้จะดูคล้ายกัน แต่ในเชิงความปลอดภัยต่างกันอย่างชี้ขาด
- การทดสอบรวมของ NixOS ใช้วิธีบูตระบบในเครื่องเสมือนแล้ว dump หน่วยความจำ เพื่อตรวจสอบว่าคีย์ถูกลบจริงระหว่าง suspend หรือไม่
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
เป็นบั๊กที่น่าสนใจจริง แต่หัวข้อให้ความรู้สึกเหมือน clickbait นิดหน่อย
เท่าที่เข้าใจ
cryptsetup luksSuspendไม่ใช่ฟีเจอร์ที่รองรับอย่างเป็นทางการเสียทีเดียว แต่ใกล้เคียงกับส่วนขยายที่ Debian ทำขึ้น ดังนั้น regression นี้น่าจะกระทบแค่ Debian หรือเปล่าไม่แน่ใจว่าจะโทษเคอร์เนลได้ไหม สำหรับฟีเจอร์ที่ไม่ได้รองรับหรือไม่ได้ทดสอบอย่างแพร่หลาย
ถึงอย่างนั้นก็น่าประทับใจ และเป็นเรื่องดีที่มีการเพิ่มเทสต์เพื่อไม่ให้ regression นี้กลับมาอีก เห็นด้วยกับความเห็นของ OP ด้วยว่า NixOSTests ยอดเยี่ยมจริง ๆ
แต่ถ้าดูจากหัวข้ออย่างเดียว มันดูเหมือนเป็นปัญหาที่แพร่หลาย ไม่ใช่แค่ดิสโทรใดดิสโทรหนึ่ง
ใช่แล้ว มันไม่กระทบคนที่ใช้การตั้งค่าเริ่มต้น เพราะพวกเขาคงไม่ได้คาดหวังตั้งแต่แรกว่าคีย์ของวอลุ่มจะปลอดภัยระหว่าง suspend
วิธีแก้ของ Debian ถูกพอร์ตไปยังดิสโทรอื่นหลายตัว หรืออาจจะส่วนใหญ่ และน่าจะมีคนจำนวนไม่น้อยที่ดูแลพอร์ตส่วนตัวของตัวเอง
หน้าแมนวล
thread-keyring(7)สัญญาว่า “thread keyring จะถูกทำลายเมื่อเธรดที่อ้างอิงถึงมันสิ้นสุดลง”โปรเจกต์ cryptsetup อาศัยคุณสมบัตินี้ในกลไกที่ย้ายคีย์จากพื้นที่ผู้ใช้ไปยังพื้นที่เคอร์เนล แต่เคอร์เนล 6.9 ได้นำ regression ที่ทำให้คุณสมบัตินี้เสียเข้ามา
เมื่อก่อนเคยลองใช้บ้างบน Arch และ openSUSE และมันมีอยู่ในดิสโทรที่ไม่ใช่ Debian อย่างแน่นอน
คงนึกถึงการผสานอัตโนมัติกับ system suspend มากกว่า แต่ประเด็นนั้นไม่ใช่แกนหลัก
luksSuspendมีเอกสารระบุว่าจะลบคีย์ออกจากหน่วยความจำระบบ และพฤติกรรมนั้นหยุดทำงานเพราะแพตช์ refactoring ดังกล่าวใน Linux 6.9อย่างไรก็ดี ในทางปฏิบัติก็อาจมองว่าเป็นบั๊กฝั่ง cryptsetup ได้เหมือนกัน เพราะมันอาศัยพฤติกรรมอายุการใช้งานที่เฉพาะเจาะจงมากของคีย์ใน keyring ของเคอร์เนล และก็สามารถโต้แย้งได้ว่าควรลบอย่างชัดเจนกว่านี้จากพื้นที่ผู้ใช้
[1]: https://gitlab.com/cryptsetup/cryptsetup/-/commit/3cea5dcc7b...
[2]: https://gitlab.com/cryptsetup/cryptsetup/-/blob/main/docs/v1...
[3]: https://gitlab.com/cryptsetup/cryptsetup/-/merge_requests/93...
คงหมายถึงอุปกรณ์ที่หลังจาก
luksSuspendแล้วสามารถทำ RAM suspend ได้ในแบบที่มีประโยชน์จริง ๆ ซึ่งตอนแรกมุ่งเป้าไปที่ Debian และภายหลังก็มีใน Arch ด้วย แต่ทั้งสองอย่างก็ไม่ใช่ค่าเริ่มต้นมองไม่เห็นวิธีอื่นที่ดีนัก ถ้า sleep หรือก็คือ RAM suspend ทุกอย่างจะถูกเก็บไว้ใน RAM และถูกเข้ารหัสก็จริง แต่จำได้ว่ามาสเตอร์คีย์ยังคงอยู่ในหน่วยความจำเคอร์เนล
ในทางกลับกัน ถ้า hibernate หรือก็คือ suspend ลงดิสก์ เนื้อหาทั้งหมดของ RAM รวมถึงมาสเตอร์คีย์จะถูกเขียนลงดิสก์และเข้ารหัส จากนั้น RAM จะถูกล้าง
ตอนปลุกกลับขึ้นมา ต้องใส่ passphrase อีกครั้งเพื่อถอดรหัสมาสเตอร์คีย์และโหลดเนื้อหาบนดิสก์กลับเข้าเมโมรี
แต่ Debian ทำแอดออนเสริมชื่อ
cryptsetup-suspendขึ้นมาก่อน ซึ่งจะรันคำสั่งluksSuspendที่ควรลบคีย์ออกจากหน่วยความจำ แล้วตอน resume จะขอ passphrase ใหม่จนถึงเคอร์เนล 6.8 มันทำงานตามที่อธิบายไว้ แต่ตั้งแต่เคอร์เนล 6.9 เป็นต้นมา มันเงียบ ๆ แล้วไม่ทำงาน
ถ้าเปิดฟีเจอร์นี้ การโจมตีแบบ cold boot ก็กลายเป็นเรื่องในอดีต แค่มักถูกปิดไว้เป็นค่าเริ่มต้นเพราะทำให้ความเร็ว RAM ลดลงประมาณ 0.5%
หลังจาก Sleep แล้วไม่ได้กรอกรหัสผ่านตอนบูตใหม่ แสดงว่าคีย์เข้ารหัสยังคงอยู่ในหน่วยความจำแน่นอน
cryptsetup-luksSuspendเรื่องนี้ไม่ใช่ปัญหาที่ผมกังวลมากนัก
เหตุผลเดียวที่ทำดิสก์เข้ารหัสคือเพื่อเวลาขายแล็ปท็อป จะได้ไม่ต้องกังวลว่ามีใครมาคุ้ยเอกสารภาษีหรือข้อมูลบัตรเครดิต
แน่นอนว่าก็ล้างแล็ปท็อปด้วย แต่ถ้าข้อมูลถูกเข้ารหัสในระดับไดรฟ์ ผมมองว่าความเสี่ยงที่จะกู้ข้อมูลด้วยเครื่องมือ forensic อะไรทำนองนั้นต่ำมาก
LUKS ใช้อัลกอริทึม anti-forensic ที่ต้องมีวอลุ่มคีย์ทั้งหมดจึงจะเปิดดิสก์ได้ โดยรวมบล็อกคีย์เข้าด้วยกันด้วยอัลกอริทึมกระจายและ XOR เพื่อสร้างมาสเตอร์คีย์จริง ๆ ดังนั้นในทางทฤษฎี แค่ลบหนึ่งเซกเตอร์ของวอลุ่มคีย์ก็ควรทำให้กู้คืนทั้งหมดไม่ได้
กล่าวคือ ถ้าขาดบล็อกใดบล็อกหนึ่งของคีย์ไป ก็ไม่สามารถเดาส่วนที่เหลือได้ง่าย ๆ
ผมไม่ใช่ผู้เชี่ยวชาญด้านความปลอดภัยเลย แต่พอเห็นว่าช่วงนี้มีการพบช่องโหว่ความปลอดภัยร้ายแรงอยู่เป็นระยะ ๆ ที่เกิดจาก “พลาดเช็ก C แค่บรรทัดเดียวที่ข้ามไฟล์กันระหว่างรีแฟกเตอร์” ก็เริ่มสงสัยสมมติฐานเรื่อง โค้ดเบส C โอเพนซอร์สขนาดใหญ่ที่ปลอดภัย เองแล้ว
นี่ไม่ใช่ปัญหาเฉพาะของ C แต่ผมมองว่าโดยเฉพาะใน C การบังคับใช้และติดตาม invariant ให้สม่ำเสมอนั้นยากกว่า และยิ่งยากขึ้นเมื่อมีการเปลี่ยนโค้ด
ก็ไม่รู้เหมือนกันว่าการเขียนโปรแกรมเชิงฟังก์ชันที่ encode invariant ไว้ใน type จะเป็นทางออกที่ขยายได้จริงในทางปฏิบัติไหม การตรวจสอบโมเดล? LLM fuzzing? องค์ประกอบพื้นฐานที่น้อยลงและมีขอบเขตชัดเจนขึ้น? seLinux ถูก “ตรวจสอบ” ด้วยวิธีแบบนั้นหรือเปล่า?
โดยพื้นฐานมันประมาณนี้:
original:
DoTheThing()new:
DoTheThingSlightlyDifferentButKeepMyCredentialsAlive()fix:
DoTheThingSlightlyDifferentButDoInFactNOTKeepMyCredentialsAlive()จากประสบการณ์ บั๊กที่รับมือยากจำนวนมากมาจากการละเมิด invariant ของระบบระดับสูง และเรื่องแบบนี้ดูไม่ใช่สิ่งที่จะทำให้เป็นอัตโนมัติได้
แม้แต่ด้วยของอย่าง Lean ก็พิสูจน์ได้ว่าโปรแกรมมีคุณสมบัติบางอย่าง แต่คุณต้องนึกถึงคุณสมบัตินั้นก่อนอยู่ดี การพิสูจน์ไม่ได้ค้นพบ invariant แทนให้
ถ้านึกถึงคุณสมบัติด้านความปลอดภัยที่เกี่ยวข้องได้แล้ว การเขียน regression test ก็คงไม่ยาก ส่วนที่ยากจริง ๆ ผมคิดว่าไม่ใช่การแสดงการ implement ให้ปลอดภัย แต่คือการตระหนักว่ามีคุณสมบัติที่ implementation ต้องรักษาไว้
ปัญหาคือ ความสามารถในการ audit ที่สูงกว่า ไม่ได้แปลว่าจะถูก audit มากขึ้นโดยอัตโนมัติ
ต้องมีคนที่มีฝีมือพอใช้เวลามากพอในการทำงานนี้
นี่เป็นบั๊กที่เกิดจาก concern ตัดกันและขาด ความรู้ข้ามโดเมน ต่อให้เป็น Lisp หรือแอสเซมบลีก็คงเป็นเหมือนกัน
กล่าวอีกอย่างคือ ไม่มีนิยามที่เคร่งครัดว่าอะไรคือประเด็นด้านความปลอดภัย
หน่วยงานรัฐบาลกลางต้องการวิธีเอาคีย์อย่างสิ้นหวังหรือเปล่า? นี่คือ bugdoor ไหม? มีการตามรอย commit แล้วหรือยัง?
ช่วงหลังเห็นแพตเทิร์นแบบนี้เยอะจนเริ่มสงสัยนิดหน่อยแล้ว อาจเป็นเพราะผู้คนไวต่อเรื่องนี้มากขึ้นและโพสต์กันมากขึ้นก็ได้
การที่คีย์เข้ารหัสอยู่ในหน่วยความจำไม่ได้แปลว่าจะดึงออกมาได้ทันที มันใกล้เคียงกับการถูกทิ้งไว้โดยไม่จำเป็นและไม่มีกำหนดในที่ที่เดิมทีไม่ควรอยู่มากกว่า
regression แบบนี้พลาดได้ง่าย เพราะทุกอย่างยังคง “ทำงาน” ต่อไป บั๊กความปลอดภัย มักไม่เผยตัวเอง
เขียนแล้วก็สนุกด้วย และทำให้สามารถรัน
git-bisectเพื่อหาการรีแฟกเตอร์เคอร์เนลเฉพาะที่นำบั๊กนี้เข้ามาได้: https://github.com/NixOS/nixpkgs/pull/532499บนแล็ปท็อป Fedora ผมตั้งค่า Linux ให้ hibernate ลงดิสก์หลัง suspend ไป 15 นาที ถ้าตัดไฟหน่วยความจำไปเลย บั๊กเฉพาะ Debian แบบนี้ก็ไม่เป็นปัญหา
ส่วนขยายเครื่องมือ Linux ของ Debian นั้นในทางทฤษฎีถือว่าดี แต่ถ้ากังวลเรื่อง cold boot attack จริง ๆ ก็ไม่ใช่แค่คีย์ LUKS เท่านั้น คีย์ทั้งหมดและเอกสารสำคัญทั้งหมดก็ควรถูกล้างออกจากหน่วยความจำด้วย
ดังนั้นวิธีที่ถูกต้องในการป้องกัน cold boot สุดท้ายก็คือ hibernate เท่านั้น
เท่าที่รู้ ถ้าไม่ใช้ TPM ก็ทำให้ใช้งานจริงได้ยาก และถ้าใช้ TPM ก็เท่ากับฝากชะตาไว้กับ TPM โดยพื้นฐาน
ลองจินตนาการดูว่าถ้าช่องโหว่นี้อยู่ใน ระบบปฏิบัติการเชิงพาณิชย์ กระทู้ HN นี้จะหน้าตาเป็นอย่างไร
คอมเมนต์ระดับบนสุดคงเป็นทำนองว่า Applosoft ไม่สนใจคุณภาพซอฟต์แวร์อีกต่อไปแล้ว หรือ “นี่แหละผลลัพธ์เมื่อยอมให้มีขยะ vibe coding ใน OS”
คอมเมนต์ถัดลงมาคงเป็นทฤษฎีสมคบคิดเกี่ยวกับกลุ่มอุตสาหกรรมเฝ้าระวังและ NSA ซึ่งที่อื่นอาจฟังดูเพี้ยน แต่ใน HN คงไม่ใช่
ไม่เข้าใจว่าทำไมของสำคัญขนาดนี้ถึงไม่ถูกทดสอบในทุก build