เทคนิคการตรวจสอบโปรแกรมสามารถป้องกันข้อบกพร่องของประเภทของ Heartbleed ไม่ให้เกิดขึ้นได้หรือไม่?


9

ในเรื่องของ Heartbleed bug บรูซชไนเออร์เขียน Crypto-Gram เมื่อวันที่ 15 เมษายนว่า '' ภัยพิบัติ 'เป็นคำที่ถูกต้อง ในระดับ 1 ถึง 10 นี่คือ 11 ' ฉันอ่านเมื่อหลายปีก่อนว่าเคอร์เนลของระบบปฏิบัติการบางระบบได้รับการตรวจสอบอย่างเข้มงวดด้วยระบบตรวจสอบโปรแกรมที่ทันสมัย ด้วยเหตุนี้จึงสามารถป้องกันไม่ให้เกิดข้อผิดพลาดในประเภทของ Heartbleed ผ่านการประยุกต์ใช้เทคนิคการตรวจสอบโปรแกรมในวันนี้หรือไม่จริงหรือแม้กระทั่งเป็นไปไม่ได้


2
นี่คือการวิเคราะห์ที่น่าสนใจของคำถามนี้โดย J. Regehr
Martin Berger

คำตอบ:


6

เพื่อตอบคำถามของคุณอย่างรัดกุมที่สุด - ใช่ข้อผิดพลาดนี้อาจถูกจับได้โดยเครื่องมือยืนยันที่เป็นทางการ แท้จริงแล้วคุณสมบัติ "ไม่ส่งบล็อกที่ใหญ่กว่าขนาดของ Hearbeat ที่ส่งไป" ค่อนข้างง่ายในการทำให้เป็นทางการในภาษาสเปคส่วนใหญ่ (เช่น LTL)

ปัญหา (ซึ่งเป็นคำวิจารณ์ทั่วไปเกี่ยวกับวิธีการที่เป็นทางการ) คือข้อกำหนดที่คุณใช้นั้นเขียนขึ้นโดยมนุษย์ อันที่จริงวิธีการอย่างเป็นทางการเพียงแค่เปลี่ยนความท้าทายในการล่าสัตว์บั๊กจากการค้นหาข้อบกพร่องเพื่อกำหนดสิ่งที่เป็นข้อบกพร่อง นี่เป็นงานที่ยาก

นอกจากนี้ซอฟต์แวร์ที่ใช้ในการตรวจสอบอย่างเป็นทางการนั้นมีความยากลำบากเนื่องจากปัญหาการระเบิดของรัฐ ในกรณีนี้มันมีความเกี่ยวข้องโดยเฉพาะอย่างยิ่งเนื่องจากหลายครั้งเพื่อหลีกเลี่ยงการระเบิดของรัฐเราสรุปขอบเขต ตัวอย่างเช่นเมื่อเราต้องการพูดว่า "ทุกคำขอตามด้วยทุนภายใน 100000 ขั้นตอน" เราจำเป็นต้องใช้สูตรที่ยาวมากดังนั้นเราจึงสรุปสูตรนั้นเป็นสูตร "ทุกคำขอจะตามมาด้วยทุน"

ดังนั้นในกรณีที่มีหัวใจสำคัญแม้ในขณะที่พยายามทำตามข้อกำหนดอย่างเป็นทางการขอบเขตที่เป็นปัญหาก็อาจถูกแยกออกไปทำให้เกิดพฤติกรรมเดียวกัน

โดยสรุปแล้วข้อผิดพลาดนี้อาจหลีกเลี่ยงได้โดยใช้วิธีการที่เป็นทางการ แต่จะต้องมีคนที่ระบุคุณสมบัตินี้ล่วงหน้า


5

โปรแกรมตรวจสอบเชิงพาณิชย์เช่น Klocwork หรือ Coverity อาจสามารถค้นหา Heartbleed ได้เนื่องจากเป็นการง่าย "ลืมที่จะทำข้อผิดพลาดในการตรวจสอบขอบเขต" ซึ่งเป็นหนึ่งในปัญหาหลักที่ออกแบบมาเพื่อตรวจสอบ แต่มีวิธีที่ง่ายกว่ามากคือใช้ชนิดข้อมูลทึบแสงที่ผ่านการทดสอบอย่างดีว่าปราศจากบัฟเฟอร์ที่มากเกินไป

มีชนิดข้อมูลนามธรรม "สตริงปลอดภัย" จำนวนหนึ่งสำหรับการโปรแกรม C หนึ่งผมส่วนใหญ่คุ้นเคยกับการเป็นVstr ผู้เขียนเจมส์ Antill มีการอภิปรายที่ดีเกี่ยวกับเหตุผลที่คุณต้องสตริงชนิดข้อมูลนามธรรมกับการก่อสร้างของตัวเองวิธีการ / โรงงานและยังเป็นรายการของสตริงนามธรรมชนิดข้อมูลอื่น ๆ สำหรับ C


2
Coverity ไม่พบ Heartbleed ให้ดูการวิเคราะห์นี้โดย John Regehr
Martin Berger

ลิงค์ที่ดี! มันแสดงให้เห็นถึงคุณธรรมที่แท้จริงของเรื่องราว: การตรวจสอบโปรแกรมไม่สามารถชดเชยกับนามธรรมที่ออกแบบมาไม่ดี (หรือไม่มีอยู่จริง)
หลงทางตรรกะ

2
ขึ้นอยู่กับสิ่งที่คุณหมายถึงโดยการตรวจสอบโปรแกรม หากคุณหมายถึงการวิเคราะห์แบบคงที่แล้วใช่ว่าจะเป็นการประมาณเสมอซึ่งเป็นผลโดยตรงจากทฤษฎีบทของข้าว หากคุณตรวจสอบพฤติกรรมเต็มรูปแบบในทฤษฎีบทแบบโต้ตอบคุณจะได้รับการรับประกันว่าโปรแกรมนั้นตรงตามข้อกำหนด แต่ก็ลำบากมาก และคุณยังคงพบปัญหาว่าข้อมูลจำเพาะของคุณอาจผิด (ดูเช่นการระเบิด Ariane 5)
Martin Berger

1
@MartinBerger: Coverity พบว่าในขณะนี้
Reinstate Monica - M. Schröder

4

หากคุณนับว่าเป็น "  เทคนิคการตรวจสอบโปรแกรม  " การรวมกันของการตรวจสอบขอบเขตของรันไทม์และการคลุมเครือใช่ว่าข้อผิดพลาดนี้อาจถูกจับได้

การคลุมเครือที่เหมาะสมจะทำให้การmemcpy(bp, pl, payload);อ่านที่น่าอับอายเกินขีด จำกัด ของบล็อกหน่วยความจำplในขณะนี้ โดยหลักการแล้วการตรวจสอบขอบเขตของรันไทม์สามารถจับการเข้าถึงใด ๆ และในทางปฏิบัติในกรณีนี้แม้แต่รุ่นดีบักmallocที่ใส่ใจในการตรวจสอบขอบเขตของพารามิเตอร์ที่memcpyจะทำงานได้ (ไม่จำเป็นต้องยุ่งกับ MMU ที่นี่) . ปัญหาคือการดำเนินการทดสอบแบบฟัซซี่บนแพ็กเก็ตเครือข่ายแต่ละประเภทต้องใช้ความพยายาม


1
ในขณะที่ความจริงโดยทั่วไป IIRC ในกรณี OpenSSL ของผู้เขียนดำเนินการจัดการหน่วยความจำภายในของตัวเองเช่นนั้นมันก็มีโอกาสน้อยมากสำหรับการmemcpyที่จะตีเขตแดนที่แท้จริงของ (ขนาดใหญ่) mallocเขตร้องขอมีพื้นเพมาจากระบบ
William Price

ใช่ในกรณีของ OpenSSL ที่มันเป็นในช่วงเวลาของข้อผิดพลาดที่memcpy(bp, pl, payload)จะต้องมีการตรวจสอบกับขอบเขตที่ใช้โดย OpenSSL ของทดแทนไม่ได้ระบบmalloc mallocกฎนั้นออกการตรวจสอบขอบเขตอัตโนมัติในระดับไบนารี (อย่างน้อยก็ไม่มีความรู้ที่ลึกซึ้งเกี่ยวกับการmallocแทนที่) ต้องมีการคอมไพล์ใหม่ด้วยตัวช่วยสร้างระดับซอร์สโดยใช้มาโคร C แทนโทเค็นmallocหรือใช้ OpenSSL ทดแทนใด ๆ และดูเหมือนว่าเราต้องการเหมือนกันmemcpyยกเว้นด้วยเทคนิค MMU ที่ฉลาดมาก
fgrieu

4

การใช้ภาษาที่เข้มงวดมากขึ้นไม่เพียง แต่ทำให้การย้ายโพสต์เป้าหมายไปจากการติดตั้งใช้งานที่ถูกต้องไปจนถึงการทำให้สเป็คถูกต้อง มันยากที่จะทำบางสิ่งที่ผิดปกติมาก ซึ่งเป็นเหตุผลที่คอมไพเลอร์จับข้อบกพร่องมากมาย

เลขคณิตของตัวชี้เนื่องจากเป็นสูตรปกตินั้นไม่ปลอดภัยเนื่องจากระบบประเภทไม่ได้หมายความว่าควรจะหมายถึงอะไร คุณสามารถหลีกเลี่ยงปัญหานี้ได้อย่างสมบูรณ์ด้วยการทำงานในภาษาที่รวบรวมขยะ (วิธีการปกติที่ทำให้คุณต้องจ่ายค่า abstraction) หรือคุณอาจเจาะจงมากขึ้นเกี่ยวกับประเภทของพอยน์เตอร์ที่คุณใช้เพื่อให้คอมไพเลอร์สามารถปฏิเสธสิ่งที่ไม่สอดคล้องกันหรือไม่สามารถพิสูจน์ได้ว่าถูกต้องตามที่เขียนไว้ นี่คือวิธีการของบางภาษาเช่น Rust

ประเภทที่สร้างขึ้นเทียบเท่ากับบทพิสูจน์ดังนั้นหากคุณเขียนระบบประเภทที่ลืมสิ่งนี้สิ่งต่าง ๆ ทุกอย่างจะผิดพลาด สมมติว่าในขณะที่เมื่อเราประกาศประเภทเราจริง ๆ แล้วหมายความว่าเรากำลังยืนยันความจริงเกี่ยวกับสิ่งที่อยู่ในตัวแปร

  • int * x; // การยืนยันผิด ๆ x มีอยู่และไม่ได้ชี้ไปที่ int
  • int * y = z; // เฉพาะจริงถ้า z พิสูจน์แล้วว่าชี้ไปที่ int
  • * (x + 3) = 5; // เฉพาะจริงถ้า (x + 3) ชี้ไปที่ int ในอาร์เรย์เดียวกันกับ x
  • int c = a / b; // เฉพาะจริงถ้า b ไม่ใช่ศูนย์เช่น: "nonzero int b = ... ;"
  • nullable int * z = NULL; // nullable int * ไม่เหมือนกับ int *
  • int d = * z; // การยืนยันที่ผิดเนื่องจาก z เป็นโมฆะ
  • if (z! = NULL) {int * e = z; } // Ok เพราะ z ไม่ใช่ null
  • ฟรี (y); int w = * y; // การยืนยันผิด ๆ เนื่องจาก y ไม่มีอยู่อีกต่อไปที่ w

ในโลกนี้พอยน์เตอร์ไม่สามารถเป็นโมฆะได้ ไม่มีการกำหนด NerePointer และไม่ต้องตรวจสอบพอยน์เตอร์เพื่อหาค่าว่าง แต่ "nullable int *" เป็นประเภทที่แตกต่างกันซึ่งสามารถแยกค่าเป็น null หรือชี้ได้ ซึ่งหมายความว่า ณ จุดที่การสันนิษฐานที่ไม่เป็นโมฆะเริ่มต้นคุณจะเข้าสู่การยกเว้นของคุณหรือลงไปที่สาขาที่ว่าง

ในโลกนี้มีข้อผิดพลาดมากมายที่ไม่มีขอบเขตเช่นกัน หากคอมไพเลอร์ไม่สามารถพิสูจน์ได้ว่ามีขอบเขตแล้วให้ลองเขียนใหม่เพื่อให้คอมไพเลอร์สามารถพิสูจน์ได้ หากไม่สามารถทำได้คุณจะต้องใส่ Assumption ด้วยตนเอง ณ จุดนั้น คอมไพเลอร์อาจพบความขัดแย้งในภายหลัง

นอกจากนี้หากคุณไม่มีตัวชี้ที่ไม่ได้กำหนดค่าเริ่มต้นคุณจะไม่มีตัวชี้ไปยังหน่วยความจำที่ไม่ได้เตรียมการ หากคุณมีตัวชี้ไปยังหน่วยความจำที่ว่างก็ควรจะถูกปฏิเสธโดยคอมไพเลอร์ ใน Rust มีพอยน์เตอร์หลายแบบที่จะทำให้การพิสูจน์ประเภทนี้เหมาะสมกับการคาดหวัง มีพอยน์เตอร์ที่เป็นเจ้าของโดยเฉพาะ (เช่น: ไม่มีนามแฝง), พอยน์เตอร์ไปยังโครงสร้างที่ไม่เปลี่ยนรูปอย่างลึกซึ้ง ประเภทการจัดเก็บเริ่มต้นไม่เปลี่ยนรูป ฯลฯ

นอกจากนี้ยังมีปัญหาในการบังคับใช้ไวยากรณ์ที่กำหนดขึ้นจริงบนโปรโตคอล (ซึ่งรวมถึงสมาชิกส่วนต่อประสาน) เพื่อ จำกัด พื้นที่พื้นผิวอินพุตให้ตรงกับที่คาดการณ์ไว้ สิ่งที่เกี่ยวกับ "ความถูกต้อง" คือ 1) ได้รับการกำจัดของทุกรัฐที่ไม่ได้กำหนด 2) ตรวจสอบให้แน่ใจตรรกะความสอดคล้อง ความยากลำบากในการเดินทางมีมากเกี่ยวกับการใช้เครื่องมือที่ไม่ดีอย่างยิ่ง (จากมุมมองของความถูกต้อง)

นี่คือเหตุผลที่สองแนวทางปฏิบัติที่เลวร้ายที่สุดคือตัวแปรทั่วโลกและ gotos สิ่งเหล่านี้ป้องกันการวางเงื่อนไขก่อน / หลัง / คงที่รอบ ๆ สิ่งใด นอกจากนี้ยังเป็นสาเหตุที่ทำให้ประเภทมีประสิทธิภาพ เมื่อประเภทแข็งแกร่งขึ้น (ในที่สุดใช้ประเภทอ้างอิงเพื่อนำมูลค่าที่แท้จริงมาพิจารณา) พวกเขาเข้าใกล้ถึงการพิสูจน์ความถูกต้องเชิงสร้างสรรค์ในตัวเอง การทำให้โปรแกรมไม่สอดคล้องกันล้มเหลวในการรวบรวม

โปรดจำไว้ว่ามันไม่ได้เป็นเพียงแค่ความผิดพลาดโง่ นอกจากนี้ยังเกี่ยวกับการปกป้องฐานรหัสจากผู้บุกรุกที่ฉลาด จะมีหลายกรณีที่คุณต้องปฏิเสธการส่งโดยไม่มีการพิสูจน์คุณสมบัติที่สำคัญที่สร้างโดยเครื่องจักรที่น่าเชื่อถือเช่น "ตามโปรโตคอลที่ระบุอย่างเป็นทางการ"


2

เครื่องมือวิเคราะห์แบบคงที่เช่น Coverity สามารถพบข้อบกพร่อง HeartBleed และที่คล้ายกัน การเปิดเผยอย่างเต็มรูปแบบ: ฉันได้ร่วมงานกับ Coverity

ดูบล็อกโพสต์ของฉันเกี่ยวกับการสอบสวน HeartBleed ของเราและการวิเคราะห์ของเราได้รับการปรับปรุงเพื่อตรวจจับได้อย่างไร:

http://security.coverity.com/blog/2014/Apr/on-detecting-heartbleed-with-static-analysis.html


1

การตรวจสอบซอฟต์แวร์อัตโนมัติ / เป็นทางการมีประโยชน์และสามารถช่วยได้ในบางกรณี แต่อย่างที่คนอื่น ๆ ได้ชี้ให้เห็นมันไม่ใช่ bullet เงิน หนึ่งอาจชี้ให้เห็นว่า OpenSSL มีความเสี่ยงต่อการเปิดแหล่งที่มาและยังใช้ในเชิงพาณิชย์และอุตสาหกรรมที่ใช้กันอย่างแพร่หลายและไม่เคยตรวจสอบอย่างหนักก่อนที่จะปล่อย (คนหนึ่งสงสัยว่ามีแม้แต่นักพัฒนาที่จ่ายเงินในโครงการ) ข้อบกพร่องถูกค้นพบโดยทั่วไปผ่านการตรวจสอบโค้ดโพสต์ - ปล่อยและเห็นได้ชัดว่าเป็นรหัสตรวจสอบก่อนเผยแพร่ - (หมายเหตุแม้ว่าอาจจะไม่มีทางที่จะติดตามใครทำรีวิวรหัสภายใน) "ช่วงเวลาที่สามารถสอนได้" พร้อมด้วย heartbleed (ในหมู่คนอื่น ๆ ) นั้นเป็นรีวิวโค้ดที่ดีกว่าก่อนที่จะปล่อยรหัสที่มีความอ่อนไหวสูง บางที OpenSSL อาจถูกตรวจสอบมากกว่านี้

bkg เพิ่มเติมจากสื่อที่ให้รายละเอียดเกี่ยวกับต้นกำเนิดของมัน:

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.