1 คะแนน โดย GN⁺ 4 시간 전 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • ymawky เป็น static HTTP server ขนาดเล็กสำหรับ macOS ที่เขียนด้วย aarch64 assembly ล้วน ๆ และใช้เฉพาะ Darwin raw syscalls โดยไม่พึ่ง libc wrappers
  • รองรับ GET, HEAD, PUT, OPTIONS, DELETE, คำขอ byte range, การแสดงรายการไดเรกทอรี, และหน้า error แบบกำหนดเอง แต่ไม่ได้ตั้งใจให้มาแทน nginx หากเป็นการตัดชั้นความสะดวกต่าง ๆ ออกเพื่อทำความเข้าใจว่าเว็บเซิร์ฟเวอร์ทำงานอย่างไร
  • ตั้งแต่การ parse request, percent-decoding, ตรวจสอบ header, แปลงค่า range, จัดการข้อผิดพลาด, ปิดไฟล์, ไปจนถึงสร้าง response ล้วนต้องเขียนเองทั้งหมด และแม้แต่งานที่ใน Python ทำได้ด้วยการแยกสตริงง่าย ๆ หรือ int(string) ก็กลายเป็นโค้ดตรวจสอบยาวหลายสิบถึงหลายร้อยบรรทัดเมื่ออยู่ใน assembly
  • เซิร์ฟเวอร์ใช้โครงสร้าง fork-on-request ที่เรียก fork() ทุกครั้งเมื่อมีการเชื่อมต่อใหม่ ทำให้เขียนง่าย แต่รองรับการเชื่อมต่อพร้อมกันได้ไม่สูง และอาจเสี่ยงต่อ slowloris จึงมีการใช้ header timeout และ body timeout ตาม Content-Length
  • PUT จะเขียนลงไฟล์ชั่วคราวชื่อ .ymawky_tmp_<pid> ก่อน แล้วค่อยสลับแทนที่เมื่อสำเร็จ พร้อมจัดการความปลอดภัยของไฟล์ระบบเอง เช่น การป้องกัน path traversal, O_NOFOLLOW_ANY, fstat64(), รวมถึง URL encoding และ HTML escaping ในการแสดงรายการไดเรกทอรี

ภาพรวมและข้อจำกัดของ ymawky

  • ymawky เป็น static HTTP server ขนาดเล็กสำหรับ macOS ที่เขียนด้วย aarch64 assembly เท่านั้น
  • ใช้เฉพาะ Darwin raw syscalls โดยไม่พึ่ง libc wrappers และไม่ใช้ไลบรารีภายนอกหรือ parser ที่มีอยู่แล้ว
  • ฟีเจอร์ที่รองรับคือ GET, HEAD, PUT, OPTIONS, DELETE, คำขอ byte range, การแสดงรายการไดเรกทอรี และหน้า error แบบกำหนดเอง
  • ข้อจำกัดของโปรเจกต์มีดังนี้
    • aarch64 assembly only
    • รองรับเฉพาะ macOS/Darwin
    • ใช้ raw syscalls only, ไม่มี libc wrappers
    • รองรับเฉพาะ static files
    • ไม่มี preexisting parsers
    • ไม่มี external libraries
  • ไม่ได้มีเป้าหมายเพื่อแทนที่ nginx แต่เป็นการตัดชั้น abstraction ที่อำนวยความสะดวกออก เพื่อให้เข้าใจว่าเว็บเซิร์ฟเวอร์ทำงานจริงอย่างไร

งานที่ต้องทำเมื่อสร้างเว็บเซิร์ฟเวอร์ด้วย assembly

  • assembly คือชั้นกลางระหว่าง machine code กับภาษาโปรแกรมระดับสูง โดยคำสั่งอย่าง mov, add, ldr, str, cmp จะสอดคล้องกับไบต์ใน binary ที่รันจริงโดยตรง
  • svc #0x80 คือรูปแบบที่มนุษย์อ่านได้ของไบต์ D4 00 10 01 ใน binary ที่รันจริง
  • ไม่มีชนิดข้อมูล string ดังนั้นสตริงจึงเป็นเพียงพื้นที่ของไบต์ที่เรียงต่อกันในหน่วยความจำ และก็ไม่มีฟีเจอร์ภาษาอย่าง struct ของ C ทำให้ต้องรู้ค่า field offset และขนาดรวมทั้งหมดด้วยตัวเอง
  • เพราะไม่มี HTTP library, automatic cleanup, exception หรือ object งานอย่าง parse request, จัดการข้อผิดพลาด, ปิดไฟล์, และสร้าง response จึงต้องเขียนเองทั้งหมด
  • ถึงโค้ดจะทำงานผิด CPU ก็จะรันต่อไปโดยไม่เตือน ดังนั้นปัญหาจึงอยู่ที่คำสั่งและการเข้าถึงหน่วยความจำที่เราเขียนเอง

raw syscalls และลำดับการทำงานของเซิร์ฟเวอร์

  • Darwin system calls

    • ymawky เรียก kernel โดยตรงแทนการใช้ libc wrappers
    • บน Darwin aarch64 หมายเลข system call จะใส่ไว้ในรีจิสเตอร์ x16 ขณะที่บน Linux aarch64 จะใส่ใน x8
    • หมายเลข system call ของ open() คือ 5 และต้องจัดวางอาร์กิวเมนต์อย่างชื่อไฟล์กับโหมดลงในรีจิสเตอร์โดยตรงก่อนเรียก kernel ด้วย svc #0x80
    • ถ้า open() ล้มเหลว carry flag จะถูกตั้งค่าไว้ และสามารถตรวจสอบแล้วกระโดดไปยังโค้ดจัดการความล้มเหลวได้ด้วยรูปแบบอย่าง b.cs open_failed
  • การทำงานพื้นฐานของเซิร์ฟเวอร์

    • ลำดับพื้นฐานของเว็บเซิร์ฟเวอร์คือรับ request, ประมวลผล, แล้วส่งกลับ status code และไฟล์ที่จำเป็น
    • การตั้งค่า socket ประกอบด้วยขั้นตอนอย่าง socket(AF_INET, SOCK_STREAM, 0), setsockopt(... SO_REUSEADDR ...), bind(sockfd, &addr, 16), listen(sockfd, 5), accept(sockfd, NULL, NULL)
    • ymawky เป็นเซิร์ฟเวอร์แบบ fork-on-request ที่เรียก fork() ทุกครั้งเมื่อมีการเชื่อมต่อใหม่
    • วิธีนี้ไม่แชร์หน่วยความจำระหว่างการประมวลผลแต่ละ request จึงเข้าใจและเขียนได้ง่าย แต่เพราะแต่ละโปรเซสมีพื้นที่หน่วยความจำของตนเอง ภาระจึงสูงกว่า และรองรับการเชื่อมต่อพร้อมกันได้น้อยกว่าโมเดล event-driven asynchronous non-blocking แบบ nginx
    • เมื่อจำนวนการเชื่อมต่อพร้อมกันเพิ่มขึ้น kernel ก็จะใช้เวลากับการสลับโปรเซสมากกว่าการรันภายในแต่ละโปรเซส
  • งานที่ต้องทำระหว่างการประมวลผล request

    • ต้องตรวจว่า request method เป็น GET, HEAD, OPTIONS, PUT, หรือ DELETE
    • ต้องดึง path ของ request แล้วถอดรหัส percent-encoding อย่าง %20
    • ต้องตรวจสอบความปลอดภัยของ path และ parse header fields ที่ client ส่งมา
    • ต้องดึงข้อมูลไฟล์ของ request เพื่อแยกว่าเป็นไดเรกทอรีหรือไฟล์ปกติ
    • request body ของ PUT ต้องเขียนลงไฟล์ชั่วคราว และต้องสร้าง response headers กับ body
    • ต้องปิดไฟล์ที่เปิดไว้และจัดการ error เพื่อไม่ให้เซิร์ฟเวอร์ crash

ลงมือเขียน HTTP parser เอง

  • request line และจุดสิ้นสุดของ header

    • HTTP request เป็นสตริงที่เซิร์ฟเวอร์ต้องตีความ ตัวอย่างเช่น
      GET /index.html HTTP/1.0\r\n
      Range: bytes=1-5\r\n\r\n
      
    • บรรทัดแรกประกอบด้วย request GET, ไฟล์เป้าหมาย index.html และเวอร์ชัน HTTP HTTP/1.0
    • \r\n คือจุดสิ้นสุดของบรรทัด และ \r\n\r\n คือจุดสิ้นสุดของ header
    • หากยังไม่ได้รับ \r\n\r\n ต้องหยุดและตอบกลับ 400 Bad Request
  • การดึง path

    • ymawky จะตรวจชนิดของ request จากเมธอดที่รองรับและไบต์เริ่มต้น จากนั้นจึงดึง path ออกมา
    • มันจะสแกน header ทีละไบต์เพื่อหา / หรือ * แต่จะตรวจด้วยว่าไบต์ก่อนหน้า / ต้องเป็นช่องว่าง เพื่อไม่ให้เข้าใจ / ใน HTTP/1.0 ว่าเป็น path
    • ตัวอย่างเช่น GET HTTP/1.0\r\n\r\n มี / อยู่ใน HTTP/1.0 ดังนั้นถ้าไบต์ก่อนหน้าไม่ใช่ช่องว่าง ก็ต้องตอบ 400 Bad Request
    • ในหลายระบบ PATH_MAX มีค่า 4096 ไบต์ ดังนั้น ymawky จึงมีบัฟเฟอร์ชื่อไฟล์ขนาด 4096 ไบต์ พร้อมอีก 1 ไบต์สำหรับ null terminator ด้วย filename_buffer: .skip 4097
    • หาก path ของ request ยาวเกินบัฟเฟอร์ ต้องตอบ 414 URI Too Long แทนการเขียนทับหน่วยความจำแบบสุ่ม
    • งานที่ใน Python ใกล้เคียงกับ text.split("GET /")[1].split(" ")[0] เมื่ออยู่ใน assembly และต้องรวมการตรวจสอบความถูกต้องของ HTTP ด้วย จะกลายเป็นโค้ดราว 200 บรรทัด
  • percent-decoding และการตรวจ header fields

    • เมื่อพบ % ใน path ต้องตรวจว่าอีกสองไบต์ถัดไปเป็นเลขฐานสิบหกที่ถูกต้องในช่วง 0-9, a-f, A-F แล้วจึงแปลงเป็นค่าไบต์นั้น
    • GET อาจมี header Range: และ PUT จำเป็นต้องมี Content-Length:
    • header เหล่านี้ไม่ได้อยู่ในตำแหน่งคงที่เหมือน URL ของ request จึงต้องวนดู header ทั้งหมดทีละตัวอักษร
    • หากมี \r ที่ไม่ตามด้วย \n หรือมี \n โดยไม่มี \r มาก่อน ต้องถือว่า header ไม่ถูกต้องและตอบ 400 Bad Request
    • หากบรรทัด header ใหม่ขึ้นต้นด้วยช่องว่าง ต้องตอบ 400 Bad Request เพราะ header field ไม่ควรเริ่มด้วยช่องว่าง
  • การเปรียบเทียบสตริงและแปลงตัวเลข

    • เพื่อหา Range: หรือ Content-Length: จึงต้องเขียนฟังก์ชัน streqn ที่รับ string pointer สองตัวคือ x0, x1 และความยาวสูงสุด x2 แล้วเปรียบเทียบทีละตัวอักษร
    • header Range: สามารถละค่าเริ่มต้นหรือค่าสิ้นสุดได้แบบนี้ แต่ต้องมีอย่างน้อยหนึ่งค่าเสมอ
      Range: bytes=10-
      Range: bytes=-10
      Range: bytes=5-10
      
    • เพราะค่าช่วงเป็นสตริง จึงต้องมีฟังก์ชันสไตล์ atoi สำหรับแปลงเลข ASCII เป็นจำนวนเต็ม
    • เพื่อหลีกเลี่ยง overflow ของรีจิสเตอร์ 64 บิต หากตัวเลขยาวตั้งแต่ 19 หลักขึ้นไปจะถือเป็น error
    • แม้งานอย่าง int(string) ใน Python ก็ยังต้องลงมือเขียนเองใน assembly ทั้งการตรวจตัวเลข การคูณ การบวก และการใช้ carry flag เพื่อส่งสัญญาณว่าสำเร็จหรือล้มเหลว

การจัดการ PUT และกลยุทธ์ไฟล์ชั่วคราว

  • PUT เป็นเมธอดแบบ idempotent ที่แม้ส่ง request เดิมหลายครั้ง สถานะสุดท้ายของเซิร์ฟเวอร์ก็ยังเหมือนเดิม
  • PUT /file.txt จะสร้าง file.txt หรือเขียนทับไฟล์เดิมทั้งหมด และแม้ส่ง 1234 สองครั้ง เนื้อหาไฟล์ก็จะเป็น 1234 ไม่ใช่ 12341234
  • การเปิด PUT แบบทั่วทั้งระบบอาจเสี่ยง และปัญหาที่ต้องคำนึงระหว่างประมวลผลมีดังนี้
    • กรณีโปรเซสล่มระหว่างจัดการ request
    • กรณี client ระบุ Content-Length ว่า 2KB แต่ส่งมาเพียง 100 ไบต์
    • กรณี client ระบุ Content-Length มหาศาล เช่น 50GB
  • MAX_BODY_SIZE ใน config.S มีค่าเริ่มต้น 1GB และหาก Content-Length เกินค่านี้ จะตอบ 413 Content Too Large
  • หากเปิดไฟล์เดิมแล้วเขียนลงไปทันที เมื่อเกิดความผิดพลาดอาจเหลือไฟล์ที่ถูกเขียนค้างไว้เพียงครึ่งเดียว ดังนั้น ymawky จึงเขียนลงไฟล์ชั่วคราวรูปแบบ .ymawky_tmp_<pid> ก่อน
  • มันใช้ system call getpid() หมายเลข 20 เพื่อเอา pid แล้วแปลงเป็นสตริงด้วย itoa() ที่เขียนเอง พร้อมตรวจบัฟเฟอร์ล้น
  • เมื่อเขียน request body จาก client ลงไฟล์ชั่วคราวครบและสำเร็จแล้ว จึงเปลี่ยนชื่อไฟล์ชั่วคราวให้เป็นชื่อปลายทาง เพื่อให้ไฟล์ที่ขอปรากฏบนเซิร์ฟเวอร์
  • หาก client ตัดการเชื่อมต่ออย่างไม่คาดคิด, timeout, หรือส่ง body ที่ไม่ถูกต้อง ไฟล์ชั่วคราวจะถูกลบด้วย system call unlink() หมายเลข 10 หรือ unlinkat() หมายเลข 472
  • ไฟล์เดิมจะถูกเขียนทับก็ต่อเมื่อ request ที่สมบูรณ์ถูกส่งสำเร็จแล้วเท่านั้น

การแสดงรายการไดเรกทอรีและการ escape

  • เมื่อได้รับ request GET /somedir/ จะตรวจว่ามีการเปิด ALLOW_DIR_LISTING ใน config.S หรือไม่
  • หากปิดการแสดงรายการไดเรกทอรีไว้ จะตอบ 403 Forbidden
  • หากเปิดไว้ จะใช้ system call getdirentries64() หมายเลข 344 เพื่อเติมบัฟเฟอร์ด้วยข้อมูลไฟล์ในไดเรกทอรีที่ร้องขอ
  • ในบัฟเฟอร์จะมีชื่อไฟล์แต่ละรายการและความยาวชื่อไฟล์ ซึ่ง ymawky นำมาใช้สร้าง HTML ที่คลิกได้
  • รูปแบบพื้นฐานที่ส่งให้ client สำหรับแต่ละไฟล์คือ
    <a href="filename">filename</a>
    
  • ชื่อไฟล์ใน href="..." ต้องถูก percent-encode ในฐานะ URL path segment ส่วนข้อความที่มองเห็นบนหน้าจอ ต้องถูก HTML-escape
  • หากชื่อไฟล์เป็น &.-~><foo ค่า href จะเป็น %26.-~%3E%3Cfoo และข้อความที่แสดงจะเป็น &amp;.-~&gt;&lt;foo ทำให้ผลลัพธ์สุดท้ายเป็นดังนี้
    <a href="%26.-~%3E%3Cfoo">&amp;.-~&gt;&lt;foo</a>
    
  • ด้วยวิธีนี้ ชื่อไฟล์อย่าง <script>something evil</script> ที่อาจทำ XSS ในส่วนเนื้อหา หรือชื่ออย่าง \"><script>something dastardly</script> ที่อาจทำ XSS ในส่วน href="..." ก็จะไม่ถูกรัน

ความปลอดภัยเครือข่ายและ timeout

  • slowloris คือการโจมตีแบบปฏิเสธการให้บริการที่เปิดการเชื่อมต่อจำนวนมากค้างไว้โดยไม่ส่ง request ให้จบ เพื่อยึดทรัพยากรของเซิร์ฟเวอร์ไว้
  • ymawky ใช้โครงสร้าง fork-on-request จึงอาจเปราะบางต่อ slowloris
  • หากไม่ได้รับ header ครบภายใน HEADER_REQ_TIMEOUT_SECS ใน config.S จะส่ง 408 Request Timeout แล้วปิดการเชื่อมต่อ
  • หากระหว่างรับ request body client ส่งข้อมูลช้าเกินไป ก็จะจัดการแบบเดียวกันตามค่า RECV_TIMEOUT ใน config.S
  • การตั้ง timeout แยกตามการอ่านแต่ละครั้งอย่างเดียวไม่เพียงพอ
    • client ที่ประสงค์ร้ายอาจส่ง Content-Length: 1073741823 แล้วส่งมา 1 ไบต์ทุก 9 วินาที ซึ่งความยาวนี้ยังน้อยกว่าค่าสูงสุดอยู่ 1 ไบต์ จึงผ่านเงื่อนไขได้ และถ้า timeout เป็นทุก 10 วินาทีก็อาจต้องรอนานเกิน 300 ปี
  • เพื่อลดปัญหานี้ ymawky จะคำนวณ timeout จาก Content-Length และจำนวนไบต์ขั้นต่ำต่อวินาที
    timeout = grace_period + content_length / min_bps
    
  • grace_period คือเวลาขั้นต่ำที่ให้กับ body ทุกก้อน และ min_bps คือความเร็วส่งข้อมูลต่ำสุดที่เซิร์ฟเวอร์ยอมรับ
  • ค่าเริ่มต้นของ min_bps คือ 16KB/s ซึ่งถือว่าผ่อนปรนแต่ไม่ไร้ขีดจำกัด
  • วิธีนี้ไม่ได้หยุดการโจมตีแบบปฏิเสธการให้บริการได้ทั้งหมด แต่ช่วยจำกัดเวลาที่ทรัพยากรถูกยึดไว้จากการโจมตีบางรูปแบบได้

ความปลอดภัยของไฟล์ระบบ

  • ลำดับการตรวจข้อมูลไฟล์

    • สำหรับ GET และ HEAD ymawky จะเปิด path ที่ร้องขอก่อน แล้วจึงเรียก system call fstat64() หมายเลข 339 กับ file descriptor เพื่อดึงข้อมูลอย่างชนิดไฟล์และขนาด
    • หากเรียก stat64() หมายเลข 338 กับ path ก่อน แล้วค่อยเปิดไฟล์ภายหลัง อาจเกิด TOCTOU race condition ที่ไฟล์ถูกเปลี่ยนไประหว่างจังหวะตรวจสอบกับจังหวะใช้งาน
  • docroot และการป้องกัน path traversal

    • ทุก path ของ request จะถูกเติม docroot ไว้ข้างหน้า
    • ค่า docroot เริ่มต้นคือ www/ ตาม DEFAULT_DIR ใน config.S
    • request /etc/shadow จึงกลายเป็น www/etc/shadow และจะได้ 404 เว้นแต่ www/etc/shadow จะมีอยู่จริง
    • แต่ /../../../../etc/shadow จะกลายเป็น www/../../../../etc/shadow ซึ่งอาจถูกตีความให้ออกนอก docroot ได้ จึงต้องมีการป้องกันเพิ่ม
    • ymawky ไม่ได้ปฏิเสธทุก path ที่มีสตริง .. แบบเหมารวม แต่จะปฏิเสธเมื่อ segment ของ path เป็น .. ตรง ๆ
    • %2E%2E จะกลายเป็น .. หลังถอดรหัส ดังนั้นการตรวจนี้ต้องทำหลัง percent-decoding
  • การจัดการ symbolic link

    • ใน POSIX แฟลก O_NOFOLLOW จะทำให้ open() ล้มเหลว หากองค์ประกอบสุดท้ายของ path เป็น symbolic link
    • ส่วน O_NOFOLLOW_ANY ของ Darwin จะทำให้ล้มเหลวหากองค์ประกอบใด ๆ ของ path เป็น symbolic link
    • หากสามารถฝัง symbolic link บางตัวไว้ใน docroot ได้ ก็น่าจะมีปัญหาอื่นอยู่ก่อนแล้ว แต่แฟลกนี้ก็ช่วยเพิ่มการป้องกันได้อีกชั้น

พฤติกรรมเฉพาะของ Apple

  • การจัดการ timeout และ sigaction()

    • หากต้องการทำ request timeout ต้องใช้ system call setitimer() หมายเลข 83 เพื่อให้ส่ง SIGALRM หลังเวลาที่กำหนด
    • โดยปกติ SIGALRM จะฆ่า child process ทันที แต่ ymawky ต้องส่ง 408 Request Timeout ก่อน
    • จึงต้องใช้ system call sigaction() หมายเลข 46
    • โครงสร้าง raw sigaction ของ Darwin เปิดเผยฟิลด์ sa_tramp
    • ปกติ libc จะเป็นผู้ตั้งค่า sa_tramp เพื่อเก็บ stack และรีจิสเตอร์ เตรียม sigreturn แล้วค่อยกระโดดไปยัง handler
    • แต่ timeout handler ของ ymawky จะส่ง 408 Request Timeout, ปิดสิ่งที่จำเป็น แล้วจบ child process ไปเลย จึงไม่จำเป็นต้อง return กลับ
    • ดังนั้นจึงตั้ง trampoline slot ให้ชี้ไปยังโค้ดที่ตอบ timeout โดยตรง เพื่อข้ามทั้ง sa_handler และ sigreturn
  • proc_info() และการจำกัดจำนวน child process

    • Apple มี system call proc_info() หมายเลข 336 ที่เอกสารไม่ชัดเจนนักสำหรับดึงข้อมูลโปรเซสที่กำลังรันและข้อมูลของ child
    • โดยทั่วไป system call นี้ถูกใช้ในเครื่องมืออย่าง ps, lsof, top
    • ymawky ใช้ proc_info() เพื่อนับจำนวน child process ที่ยังทำงานอยู่
    • เพราะสามารถตั้งค่าจำนวนการเชื่อมต่อสูงสุดได้ จึงต้องรู้ว่ามี child ที่ยังมีชีวิตอยู่กี่ตัว
    • proc_info() จะเขียนข้อมูล child process ลงในบัฟเฟอร์ และเพราะรู้ขนาดของแต่ละรายการอยู่แล้ว จึงคำนวณจำนวน child ได้จากจำนวนไบต์ที่เขียนกลับมา
    • หากจำนวน child เกิน MAX_PROCS การเชื่อมต่อใหม่จะถูกปฏิเสธด้วย 503 Service Unavailable

บทสรุปและข้อมูลโปรเจกต์

  • สำหรับ static web server ส่วนที่ยากกว่าเรื่องเปิด socket และ listen คือการ parse request และจัดการเงื่อนไขขอบทั้งหมด
  • request, path, และ response ล้วนเป็นไบต์ทั้งหมด คำขอ range ต้องแม่นยำ และชื่อไฟล์ก็ต้อง escape ต่างกันตามตำแหน่งที่ถูกนำไปใช้
  • assembly บังคับให้ต้องเขียนทุกอย่างเอง ตั้งแต่ request parsing, memory management, error handling, string conversion, timeout ไปจนถึง file safety
  • ymawky ดูแลโดย imtomt

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

 
GN⁺ 4 시간 전
ความคิดเห็นจาก Lobste.rs
  • สุดยอดมาก เมื่อก่อนฉันเคยทำงานเชื่อมต่อกับบริษัทเล็ก ๆ ที่ทำอุปกรณ์อัจฉริยะอยู่แห่งหนึ่ง และวิศวกรเพียงคนเดียวของบริษัทนั้นรู้แค่ assembly language เท่านั้น
    ตั้งแต่โค้ดควบคุมฮาร์ดแวร์, ระบบปฏิบัติการของเซิร์ฟเวอร์ ไปจนถึง JSON web API ที่เราใช้งาน ล้วนเขียนขึ้นเองด้วยแอสเซมบลีทั้งหมด
    ครั้งหนึ่งเราเจอบั๊กที่ web API ส่งคืนข้อมูลของอุปกรณ์ผิดตัว แล้วสุดท้ายก็พบว่าเป็นข้อผิดพลาดแบบ off-by-one ในระบบจัดตารางของระบบปฏิบัติการ ทำให้ “ฐานข้อมูล” ส่งคืนแถวที่ผิดให้กับเว็บเซอร์วิส

    • หรือว่าคนนั้นชื่อ Mel?
  • เวลาพูดถึงคำอย่าง “การฆ่าตัวตาย” ช่วยใส่ คำเตือนเนื้อหา หน่อยเถอะ ถ้าจะให้ดีกว่านั้นก็คือไม่ต้องพูดถึงเลย

    • อะไรนะ? ฉันอ่านบทความแบบผ่าน ๆ ไปบ้าง แต่ตอนอ่านรอบแรกไม่เห็น การพูดถึงการฆ่าตัวตาย เลย
      พอมาเห็นคอมเมนต์นี้ก็กลับไปหาอีกรอบแล้วก็ยังไม่เจอ ฉันพลาดอะไรไปหรือเปล่า?
    • ฝั่งที่ไม่มีอารมณ์ขันเลยต่างหากที่อันตรายต่อสุขภาพของตัวเองและต่อสังคมโดยรวมมากกว่าเยอะ
  • พอเห็นคำว่า “เขียนทุกอย่างด้วยแอสเซมบลี” ก็ทำให้นึกถึง รายงานการสอบสวน Therac-25