1 คะแนน โดย GN⁺ 11 시간 전 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • Anubis กำลังออกแบบให้การขยาย proof-of-work สำหรับปกป้องเว็บไซต์ออกไปนอกเหนือจาก SHA-256 โดยให้ไคลเอนต์และเซิร์ฟเวอร์รัน ตรรกะการตรวจสอบ WebAssembly ชุดเดียวกัน
  • เพื่อไม่ตัดสภาพแวดล้อมที่ปิด WebAssembly ออกไป จึงเตรียม เส้นทางรีคอมไพล์เป็น JavaScript ไว้ แต่จะช้ากว่า WebAssembly และถ้า JIT ถูกปิดไปด้วยก็อาจช้าลงอีก
  • wasm2js ในดิสโทร Linux นั้นเก่า ทำให้ผลลัพธ์ต่างจากเวอร์ชัน Homebrew จึงต้องบันเดิล wasm2js ที่บิลด์ด้วย wasi-sdk เพื่อให้ได้บิลด์ที่ทำซ้ำได้
  • การบิลด์ C/C++ อาจทำให้ เอาต์พุตระดับไบต์ แกว่งได้แม้ใช้อินพุตเดียวกัน จาก __DATE__, __TIME__, wasm-opt ใน $PATH และลำดับพอยน์เตอร์ของโค้ดจัดการข้อยกเว้น
  • การติดตั้งใช้งานสุดท้ายใช้ --no-wasm-opt, setarch --addr-no-randomize, การตรวจสอบ SHA-256 แยกตาม x86_64·arm64 และการยืนยันรีบิลด์ใน CI เพื่อให้ได้ ความเป็นกำหนดแน่นอนภายในสถาปัตยกรรมเดียวกัน

proof-of-work แบบ WebAssembly ของ Anubis และเส้นทางสำรองด้วย JavaScript

  • Anubis ต้องการเพิ่ม การตรวจสอบ proof-of-work บนพื้นฐาน WebAssembly เพื่อให้ผู้ดูแลระบบใช้วิธี proof-of-work ที่ไม่ใช่ SHA-256 ในการปกป้องเว็บไซต์ได้
  • เป้าหมายหลักคือไม่ต้องแยกอิมพลีเมนต์ตรรกะการตรวจสอบสำหรับไคลเอนต์และเซิร์ฟเวอร์ แต่ นิยามไว้เพียงที่เดียว
    • ไคลเอนต์และเซิร์ฟเวอร์เชื่อมต่อกับ WebAssembly ชุดเดียวกันเพื่อรันตรรกะการตรวจสอบ
    • มุ่งไปสู่โครงสร้างที่ตรวจสอบได้ว่าทั้งสองฝั่งทำงานแบบ lockstep กัน
  • ไคลเอนต์ที่ปิด WebAssembly ก็ยังอยู่ในขอบเขตที่ต้องรองรับ
    • มีข้อจำกัดว่าไม่ต้องการกีดกันผู้ใช้ออกจากเว็บไซต์โดยพฤตินัย
    • Anubis ต้องหาสมดุลระหว่างประสบการณ์ผู้ใช้ ประสบการณ์ผู้ดูแลระบบ และประสบการณ์นักพัฒนา
  • ทางอ้อมที่เลือกคือการคอมไพล์ WebAssembly กลับเป็น JavaScript อีกครั้ง
    • ได้แรงบันดาลใจจาก The Birth and Death of JavaScript
    • JavaScript ที่ได้จะช้ากว่า WebAssembly ที่เทียบเท่ากัน
    • การปิด WebAssembly บางครั้งทำให้ JavaScript JIT ถูกปิดตามไปด้วย จึงอาจช้าลงอีก
    • ยังต้องมีการศึกษาต่อว่าในฮาร์ดแวร์สเปกต่ำ วิธีนี้จะมีประสิทธิภาพกว่าการใช้ JavaScript แบบเดิมหรือไม่

ทำไมจึงต้องบันเดิล wasm2js

  • เครื่องมือที่ต้องใช้คือ wasm2js จากโปรเจกต์ binaryen
  • wasm2js มีแพ็กเกจอยู่ในดิสโทร Linux แต่เวอร์ชันของดิสโทรเก่าเกินไป จึงไม่สามารถสร้างผลลัพธ์แบบเดียวกับเวอร์ชัน Homebrew ในสภาพแวดล้อมพัฒนาได้
  • สำหรับบิลด์ที่ทำซ้ำได้ ความเป็น กำหนดแน่นอน ของผลลัพธ์เป็นสิ่งจำเป็น
    • หากต้องการให้ผู้ใช้และผู้แพ็กเกจเชื่อถือไบนารี wasm2js ที่คอมมิตไว้ในรีโพของ Anubis ก็ต้องสามารถบิลด์เวอร์ชันเดียวกันเองแล้วได้ไบต์เดียวกัน
    • หากเป็นไปได้ ก็ควรได้ไบต์เดียวกันบนเครื่องของคนอื่นด้วย
  • เพื่อสิ่งนี้จึงรวมสำเนา wasm2js ที่บิลด์ด้วย wasi-sdk สำหรับเป้าหมาย WebAssembly เอาไว้

จุดที่ความสามารถในการทำซ้ำพังได้ง่ายในการบิลด์ C/C++

  • ต่อให้ใส่ซอร์สไบต์เดิมและอินพุตเดิม เอาต์พุตของคอมไพเลอร์ก็ไม่ได้ออกมาเป็นไบต์เดิมเสมอไป
  • ใน C/C++ เพียงใช้แมโครในตัวอย่าง __DATE__ และ __TIME__ ก็ทำให้เกิด เอาต์พุตที่ไม่เป็นกำหนดแน่นอน ได้
    • ตัวอย่าง hello.cpp ถูกเขียนให้พิมพ์วันที่และเวลาของจังหวะที่บิลด์
    • บิลด์หนึ่งพิมพ์ Jun 18 2026 00:00:59 และอีกบิลด์พิมพ์ Jun 18 2026 00:01:11
    • ซอร์สโค้ดเป็นไบต์เดียวกัน แต่เอาต์พุตของคอมไพเลอร์ต่างกัน
  • คอมไพเลอร์ขนาดเล็กมากอาจเป็นไปได้ในทางทฤษฎีว่าจะกำหนดแน่นอนได้ แต่ในคอมไพเลอร์จริงมีตัวแปรที่ซับซ้อนกว่านั้นมาก

ปัญหาที่ Clang เรียก wasm-opt จาก $PATH แบบเงียบ ๆ

  • ใน binaryen นอกจาก wasm2js แล้ว ยังมี wasm-opt สำหรับปรับแต่งเอาต์พุตจากคอมไพเลอร์ WebAssembly ให้เหมาะขึ้น
  • Clang จะ shell out ไปเรียก wasm-opt ระหว่างการบิลด์
    • โดยทั่วไปถือเป็นพฤติกรรมที่สมเหตุสมผลเพื่อปรับปรุงประสิทธิภาพ
    • แต่ครั้งนี้ความต่างของเวอร์ชัน wasm-opt ที่อยู่ใน $PATH ทำลายความสามารถในการทำซ้ำ
  • wasm-opt บน DGX Spark เป็น version 108 ที่ /usr/bin/wasm-opt ส่วน wasm-opt จาก Homebrew บนเวิร์กสเตชันเป็น version 130
  • wasi-sdk และ binaryen พึ่งพา WebAssembly Exceptions extension
    • ตาม Can I use ผู้ใช้เบราว์เซอร์ 93.86% ใช้เอนจินเบราว์เซอร์ที่รองรับสิ่งนี้
    • C++ เป็นภาษาที่ใช้ข้อยกเว้นมาก ดังนั้นการจัดการข้อยกเว้นแบบเนทีฟของ WebAssembly จึงช่วยลด boilerplate ได้
  • ทั้ง wasmtime และ wazero ต้องเปิดการรองรับข้อยกเว้นอย่างชัดเจน
    • ใน wasmtime สามารถส่ง -W exceptions=y ได้
    • ใน wazero ต้องมี runner harness แบบกำหนดเอง
  • wasm-opt เวอร์ชันเก่าบนเครื่อง arm จะหยุดทำงานเมื่อเจอคำสั่งจัดการข้อยกเว้น ทำให้บิลด์ล้มเหลว
  • จึงส่ง --no-wasm-opt ในขั้นตอนลิงก์เพื่อตัด เส้นทางที่ไม่ทำซ้ำได้นี้ ออกไป

การจัดวางแอดเดรสส่งผลต่อการสร้างโค้ดจัดการข้อยกเว้นอย่างไร

  • Clang เวอร์ชันที่ใช้อยู่แสดงพฤติกรรมสร้างโค้ดที่ไวต่อแอดเดรสในเส้นทางจัดการข้อยกเว้นระหว่างคอมไพล์ wasm2js
  • ค่าพอยน์เตอร์ดิบมีผลต่อลำดับเอาต์พุตของบางบล็อก try_table
    • ทุกครั้งที่บิลด์จะเกิดความต่างประมาณ 29 ไบต์
    • การคำนวณแทบเหมือนเดิม แต่ลำดับไบต์เปลี่ยนไป และการอ้างอิง catch ก็เปลี่ยนตาม
  • แม้จะบิลด์ wasm2js เวอร์ชันตรึงเดียวกันบนเครื่อง arm64 ลำดับการวนพอยน์เตอร์ก็ยังต่างจากเวิร์กสเตชัน ทำให้เกิดปัญหาเดียวกัน
  • มีวิธีหลบเลี่ยงอยู่สองอย่าง
    • ปิด การสุ่มพื้นที่แอดเดรส สำหรับบิลด์นั้นด้วย setarch --addr-no-randomize
    • สร้างเช็กซัม SHA-256 known-good แยกสำหรับ x86_64 และ arm64 บนเครื่องที่เชื่อถือได้
  • CI จะรัน ./build.sh ใน ./utils/wasm/wasm2js แล้วตรวจสอบเช็กซัม
    • ถ้าตรงกับ shasums.x86_64 ก็ถือว่าผ่านเช็กซัม x86_64
    • ถ้าตรงกับ shasums.arm64 ก็ถือว่าผ่านเช็กซัม arm64
    • ถ้าไม่ตรงทั้งคู่ จะพิมพ์ SHA-256 ของ wasm-opt_130.wasm และ wasm2js_130.wasm แล้วล้มเหลว
  • งาน CI นี้รันทั้งบนโฮสต์ x86_64 และ arm64
  • ความสามารถในการทำซ้ำข้ามโฮสต์ทั้งหมดยังทำไม่ได้ และปัญหานี้ยังคงเป็นบั๊กฝั่ง upstream ของ LLVM
  • อย่างน้อยในสถานะปัจจุบัน บิลด์ก็ทำงานแบบกำหนดแน่นอนได้ ภายในสถาปัตยกรรมเดียวกัน

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

 
ความคิดเห็นจาก Lobste.rs
  • เพิ่งรู้เป็นครั้งแรกว่า clang แอบเรียก wasm-opt จาก $PATH และรู้สึกว่ามันไร้เหตุผลมาก
    เลยตรวจดูว่ามันส่งผลกับ zig cc ด้วยไหม และโชคดีที่มันจะถูกรันเฉพาะตอนใช้ clang เป็น linker driver เท่านั้น จึงไม่เข้าข่ายนี้
    ถ้า clang จัดลำดับโดยอาศัยการจัดวางแอดเดรส ส่วนตัวผมมองว่านี่เป็น บั๊ก และถ้ายังทำซ้ำได้ในรีลีสล่าสุดก็น่าจะรายงานแบบนั้น

    • Xe บอกว่าจะไปรายงาน upstream จากที่อื่น และนี่เป็น บั๊กด้านความเป็นเชิงกำหนดของ LLVM อย่างแน่นอน
      ความพยายามในการกำจัดปัญหาแบบนี้ดำเนินมาหลายปีแล้ว
    • ถ้าลองใช้ clang.exe บน Windows เป็น cross-compiler แบบเสถียร จะยิ่งน่าหงุดหงิดเข้าไปอีก
      clang มีสมมติฐานราว 500 อย่างว่ามันจะถูก build สำหรับระบบ native
  • ไม่ได้จะวิจารณ์นะ เพราะเคารพที่เป็นโอเพนซอร์สและเจ้าของโพสต์ให้บริการยอดนิยมฟรี
    ถึงอย่างนั้นก็เกลียดที่เว็บกำลังเปลี่ยนไปในทางนี้จริง ๆ เดี๋ยวนี้พอเข้าเว็บก็มักจะเจอ หน้าโหลดของ Anubis โผล่ขึ้นมา ไม่แน่ใจเลยว่าเราอยากได้เว็บที่แต่ละเว็บยอดนิยมต้องมีหน้าจอ proof-of-work เด้งขึ้นมาหรือเปล่า
    ก็ไม่รู้เหมือนกันว่ามีทางเลือกอะไรบ้างเมื่อ AI crawler ถาโถมเข้ามาไม่หยุด แต่ก็ยังสงสัยว่ามีหลักฐานไหมว่า proof-of-work ขัดขวาง AI crawler ได้จริง เพราะพวกนี้มีเงินทุนมหาศาล และยังใช้พลังประมวลผลเพื่ออ่านหน้าเว็บมากกว่านี้อยู่แล้ว ดังนั้นต้นทุนในการแก้ proof-of-work ดูเล็กน้อยมาก

    • มีหลักฐานว่า proof-of-work ขัดขวาง AI crawler ได้ เรื่องนี้ก็เคยถูกโพสต์ที่นี่หลายครั้ง
      ในโครงการนำร่อง Anubis มันเป็นตัวยับยั้งทราฟฟิกไม่พึงประสงค์ที่มีประสิทธิภาพชัดเจน และด้วยกฎที่แทบจะเป็นค่าพื้นฐานก็ยังบล็อกคำขอของแอปพลิเคชันได้ราว 90% อย่างต่อเนื่อง โดย DDR อยู่ที่ 71.0%, ArcLight 94.6% และ Catalog 92.4%
      วันที่ 30 พฤษภาคม ทราฟฟิกบอตพุ่งสูงขึ้น ทำให้ Catalog แทบใช้งานไม่ได้จนกว่าจะติดตั้ง Anubis ในวันที่ 3 มิถุนายน และที่จุดพีควันที่ 1 มิถุนายน มีคำขอ HTTP 3.4 ล้านครั้งจาก IP ไม่ซ้ำกัน 2.1 ล้านรายการ พร้อมเวลาโหลดหน้าเกิน 70 วินาที หลังติดตั้ง Anubis เมื่อวันที่ 4 มิถุนายน ก็กลับมาให้บริการผู้ใช้ได้อีกครั้ง โดยจำนวนคำขอทั้งหมดที่แอปพลิเคชันประมวลผลอยู่ที่ 125,000 ครั้ง และเวลาโหลดหน้าดีขึ้นเป็น 2.12 วินาที
      https://lobste.rs/s/ncyfcp/anubis_pilot_project_report_june_2025
      อีกกรณีหนึ่งก็ระบุว่าปัญหาหมดไปทันทีหลัง deploy Anubis และเห็นจังหวะนั้นได้ชัดเจนในระบบมอนิเตอร์ หลังจากนั้นก็ไม่มีการแจ้งเตือนอีกเลย แม้ว่าการโจมตียังคงดำเนินต่อไป แต่ภาระบนเซิร์ฟเวอร์อยู่ในระดับต่ำสุด จึงมองว่า Anubis ไม่ได้แค่บล็อก AI scraper แต่ยังทำหน้าที่เป็น การป้องกัน DDoS ด้วย
      https://lobste.rs/s/67ijih/day_anubis_saved_our_websites_from_ddos
    • ไม่จำเป็นต้องใช้ proof-of-work เสมอไป ผม deploy ตัวบล็อกแบบ stateless ที่ไม่ต้องใช้ JS ไว้ที่ https://shithub.us และถ้า scraper จะเลี่ยงได้ก็น่าจะต้องเสียต้นทุนพอ ๆ กับ proof-of-work แบบธรรมดา
      https://orib.dev/tmp/bandwidth.png
    • ไม่มีใครรู้ชัดว่าจริง ๆ แล้ว crawler คืออะไรและมีไว้เพื่ออะไร แต่ crawler จำนวนมากค่อนข้างขี้เกียจและรับมือกับสิ่งผิดปกติไม่ค่อยได้
      บางคนกันพวกมันออกได้ด้วยแค่แท็ก meta refresh หรือปุ่มที่ต้องกด ดังนั้น Anubis จึงได้ผล แต่หัวใจสำคัญไม่ใช่ proof-of-work เอง หากเป็นพฤติกรรมที่คาดไม่ถึงต่างหาก
    • ผมไม่อยากได้เว็บแบบนี้แน่นอน
      มันกำลังทรมานยิ่งกว่าสมัยที่ใช้เว็บด้วยเบราว์เซอร์ที่ปิด JavaScript ไว้อีก เว็บควรเป็นสื่อที่เน้นเอกสารธรรมดา แต่ตอนนี้ไม่ว่าที่ไหนก็ต้องผ่านด่าน Cloudflare, Anubis หรือ captcha
    • เป็นที่รู้กันตั้งแต่แรกแล้วว่า APT ใด ๆ ก็สามารถเร่งการคำนวณคำตอบของ Anubis ได้ แม้แต่ proof of concept เมื่อปีก่อนก็เขียนไว้แบบนั้น
      เนื้อหาคือบอตจะหาวิธีหลบเลี่ยง WAF ได้เสมอ และผู้ใช้จริงก็ลงเอยด้วยการเสีย CPU cycle ไปกับหน้าจอโหลด
  • น่าเสียดาย แต่ก็ไม่ได้น่าแปลกใจ ประวัติของ toolchain สำหรับคอมไพเลอร์นั้นพึ่งพาความสัมพันธ์แฝงไร้สาระในทำนองว่า “บริบทในเครื่องต้องถูกต้องอยู่แล้ว” มายาวนานมาก
    เพียงแต่ LLVM เองกลับเป็นฝ่ายที่ผลักดันการลบ dependency แบบนี้มาโดยตลอด จึงรู้สึกแปลกที่มาเห็นสิ่งนี้ใน clang ผลที่ตามมาคือ ตัวอย่างเช่น Rust compiler จึงทำงานได้โดยแทบไม่ต้องมีแนวคิดเรื่อง cross-compiler แบบแยกต่างหาก
    ถ้าลอง bootstrap ระบบปฏิบัติการโดยไม่พึ่ง build tool เดิม ๆ จะเห็นชัดทันที การสร้างเคอร์เนล สร้าง libc และคอมไพเลอร์สำหรับเคอร์เนลนั้นให้รันได้ แล้วค่อย build ทุกอย่างใหม่ทั้งหมดบน OS ใหม่นั้น เป็นงานที่ซับซ้อนและเปราะบางอย่างเหลือเชื่อ เต็มไปด้วยสมมติฐานที่ไม่ได้พูดออกมาตรง ๆ
    มันเป็นปัญหาที่พบไม่บ่อยและส่วนใหญ่อยู่ในโลกของนักพัฒนา OS กับคอมไพเลอร์ จึงแทบไม่มีทั้งเครื่องมือดี ๆ และแนวปฏิบัติที่เป็นแบบอย่าง และสำหรับคู่ผสมคอมไพเลอร์+OS แต่ละแบบ ก็น่าจะมีคนบนโลกที่เข้าใจภาพรวมทั้งหมดจริง ๆ แค่ประมาณ 5 คน

    • ฟังแล้วแปลกใจนะ ผมเคยคิดว่า LLVM ต่างจาก GCC ตรงที่ออกแบบมาโดยคำนึงถึง การคอมไพล์ข้ามแพลตฟอร์ม ผ่านการทำ abstraction ระหว่างโฮสต์กับเป้าหมาย
      และก็คิดว่า toolchain ของ Zig ได้ความสามารถบางส่วนแบบนั้นมาจาก LLVM ด้วย แน่นอนว่าเข้าใจว่าพวกเขาทำงานหนักมากเพื่อแยกส่วนต่าง ๆ ให้สะอาดขึ้น เลยสงสัยเหมือนกันว่าตอนนี้ยังใช้ LLVM อยู่หรือเปล่า
      แต่ถ้า clang เองก็มีปัญหาเดียวกัน ก็ชวนให้คิดว่าไม่ได้รับโครงสร้างที่สะอาดกว่ามาจาก LLVM เท่าไรนัก
  • เข้าใจว่าใช้ Nix อยู่ เลยสงสัยว่าทำไมถึงไม่พูดถึงหรือใช้ Nix เพื่อลดความผันผวนของสภาพแวดล้อมลงบ้าง
    อย่างเช่นปัญหา wasm-opt ใน $PATH ก็ดูเหมือนจะบรรเทาได้ด้วย Nix หรือใช้ไปแล้วแต่ผมอ่านตกไป?

  • ตอนแรกคิดใส ๆ ว่าการย้าย wasm ไปเป็น asm.js น่าจะ “ง่าย” แต่วันนี้ได้รู้อะไรใหม่เลย

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