1 คะแนน โดย GN⁺ 3 시간 전 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • FFmpeg ซึ่งใช้ประมวลผลสื่อในเบราว์เซอร์และโครงสร้างพื้นฐานสตรีมมิงทั่วโลก เป็นพื้นผิวการโจมตีที่มีความสำคัญต่อความปลอดภัย เพราะต้องพาร์สอินพุตที่ซับซ้อนและไม่น่าเชื่อถือ
  • เอเจนต์ความปลอดภัย อัตโนมัติของ depthfirst ค้นพบซีโร่เดย์ 21 รายการในโค้ด C ที่ผ่านการปรับแต่งอย่างหนักราว 1.5 ล้านบรรทัด โดยมีค่าใช้จ่ายประมาณ $1k หรือราว 10% ของค่าใช้จ่าย $10k ที่ Anthropic ใช้กับ Mythos
  • สิ่งที่ค้นพบกระจายอยู่ในหลายคอมโพเนนต์ เช่น TS demuxer, VP9 decoder และเส้นทางการประมวลผล RTP/RTSP/RTMP โดยช่องโหว่บางรายการแฝงอยู่มานานถึง 15–20 ปี
  • ช่องโหว่ใน AV1 RTP depacketizer นำไปสู่ PoC ที่เขียนทับ function pointer ได้ด้วย RTP packet ขนาดเพียง 183 ไบต์ และเข้าถึงได้เพียงแค่รัน ffmpeg -i rtsp://attacker/stream
  • การตรวจสอบความปลอดภัยที่ใช้งานได้จริงต้องไปไกลกว่าคำเตือนเชิงทฤษฎี โดยต้องมีอินพุตที่ทำซ้ำได้และยืนยันการทำงานจริง และการวิเคราะห์แบบเอเจนต์สามารถนำมาใช้ค้นหาช่องโหว่ที่ซ่อนอยู่ในโค้ดเบส C ขนาดใหญ่ได้โดยตรง

ภาพรวม

  • FFmpeg เป็นซอฟต์แวร์ที่ถูกใช้งานอย่างแพร่หลายสำหรับประมวลผลสื่อในเบราว์เซอร์และโครงสร้างพื้นฐานของแพลตฟอร์มสตรีมมิงขนาดใหญ่
  • เนื่องจากเป็นไลบรารีที่ต้องพาร์สสื่อที่ซับซ้อนและไม่น่าเชื่อถืออย่างต่อเนื่อง จึงมีความสำคัญด้านความปลอดภัยและเป็นเป้าหมายหลักของการโจมตีแบบ zero-click
  • รีโพซิทอรีของ FFmpeg ประกอบด้วยโค้ด C ที่ผ่านการปรับแต่งอย่างหนักประมาณ 1.5 ล้านบรรทัด และพาร์สรูปแบบสื่อซับซ้อนหลายร้อยชนิด
  • FFmpeg ผ่านการ fuzzing และการตรวจสอบด้วยมือมานานกว่า 20 ปี และล่าสุดทีม Google Big Sleep ได้เปิดเผย ช่องโหว่ 13 รายการ ใน FFmpeg
  • Anthropic สแกน FFmpeg ด้วยโมเดล Mythos และพบ ปัญหาด้านความปลอดภัยบางส่วน
  • หลังจากงานก่อนหน้าเหล่านี้ การค้นหาช่องโหว่ใหม่ใน FFmpeg ก็ยากขึ้น และกลายเป็นเป้าหมายสำหรับพิสูจน์ความสามารถของระบบแบบเอเจนต์ที่สแกนโค้ดเบสขนาดใหญ่ได้อย่างลึกซึ้ง

เอเจนต์ความปลอดภัยของ Depthfirst

  • เอเจนต์เขียนโค้ดและเอเจนต์ความปลอดภัยอาจใช้โมเดลพื้นฐานเดียวกันได้ แต่เป้าหมายแตกต่างกันมาก
  • เอเจนต์เขียนโค้ด มักรับโจทย์จากมนุษย์และมุ่งเขียนโค้ดแอปพลิเคชัน
  • เอเจนต์ความปลอดภัย มีบทบาทที่แคบและมุ่งเป้าชัดเจนกว่า คือค้นหาปัญหาความปลอดภัยที่สามารถถูกใช้โจมตีได้จริงในระบบที่มีอยู่ โดยไม่มีคำสั่งเฉพาะเจาะจง
  • เอเจนต์ความปลอดภัยต้องทำความเข้าใจสถาปัตยกรรมของโค้ดเบส, parser และ protocol handler ที่เปิดรับภายนอก, รวมถึงจุดที่อินพุตซึ่งผู้โจมตีควบคุมได้ไหลเข้าสู่ระบบก่อน
  • จากนั้นจะไม่ปฏิบัติต่อรีโพซิทอรีเป็นเพียงกองไฟล์แบบแบน ๆ แต่จะติดตาม การไหลของข้อมูล จากโค้ดในพื้นผิวการโจมตีไปยังคอมโพเนนต์ที่เกี่ยวข้อง
  • เอเจนต์ความปลอดภัยที่ใช้งานได้จริงต้องมี guardrail เพื่อไม่ให้จินตนาการเงื่อนไขที่ขาดหายไป ขยายความบั๊กเชิงทฤษฎีเกินจริง หรือสร้าง false positive จำนวนมาก
  • ต้องตรวจสอบว่าผู้โจมตีควบคุมอินพุตที่ถูกต้องได้จริงหรือไม่ เข้าถึงเส้นทางที่มีช่องโหว่ได้จริงหรือไม่ และข้อบกพร่องที่สงสัยสามารถทำซ้ำได้หรือไม่
  • เมื่อจำเป็น ต้องระบุหรือสร้าง harness สำหรับโต้ตอบกับคอมโพเนนต์เป้าหมายเพื่อทดสอบสมมติฐานให้เป็นรูปธรรม
  • เอเจนต์ความปลอดภัยเฉพาะทางของ depthfirst วิเคราะห์โค้ดอย่างลึก และแตกแขนงสมมติฐานหลายชุดไปทดสอบแบบขนาน
  • ผลลัพธ์ที่ได้ไม่ใช่รายงานเชิงทฤษฎีหรือคำเตือนกำกวม แต่เป็นปัญหาความปลอดภัยที่ยืนยันการทำงานจริงด้วยอินพุตเฉพาะที่ทำซ้ำได้

ผลการค้นพบ

  • เอเจนต์ค้นพบ 21 ซีโร่เดย์ ครอบคลุมตั้งแต่ TS demuxer ไปจนถึง VP9 decoder
  • ค่าใช้จ่ายรวมอยู่ที่ประมาณ $1k ซึ่งเป็นเพียงราว 10% ของค่าใช้จ่ายที่ Anthropic ใช้กับ Mythos
  • การเปรียบเทียบค่าใช้จ่าย: {b:1,10}
  • ช่องโหว่ที่ได้รับการจัดสรร CVE

    • CVE-2026-39210 คือ heap buffer overflow ใน TS demuxer ที่ขาดการตรวจขอบเขตความยาวก่อนอ่าน 2 ไบต์ และถูกเพิ่มเข้ามาในปี 2010
    • CVE-2026-39211 คือ integer overflow จากสูตรตัวคูณขนาดที่ไม่มีขอบเขตบนระหว่างการรีแฟกเตอร์ swscale ทำให้พารามิเตอร์ที่ผู้ใช้ควบคุมสามารถก่อให้เกิดการสเกลขนาดใหญ่ตามอำเภอใจได้ และถูกเพิ่มเข้ามาในปี 2010
    • CVE-2026-39212 คือ stack overflow ใน ffmpeg_opt.c ที่ไฟล์ preset สามารถกระตุ้นการพาร์สออปชันแบบเรียกซ้ำได้โดยไม่มีการจำกัดความลึก และเป็น regression ที่เข้ามาในเดือนกรกฎาคม 2025
    • CVE-2026-39213 คือ heap buffer overflow ในเส้นทางอินพุต rawvideo ของ yuv4mpegenc ที่ขาดการตรวจสอบมิติเทียบกับขนาด packet และถูกเพิ่มเข้ามาในปี 2023
    • CVE-2026-39214 คือ stack buffer overflow ที่ implementation เดิมของ SDT เขียน service entry โดยไม่ติดตามพื้นที่ที่เหลือ และถูกเพิ่มเข้ามาตั้งแต่ปี 2003 โดยแฝงอยู่นาน 23 ปี
    • CVE-2026-39215 คือ heap buffer overflow จากข้อผิดพลาดทางตรรกะใน update_mb_info() ที่ทำให้การเรียกภายหลังเขียนเกินท้ายบัฟเฟอร์ที่จัดสรรไว้ 12 ไบต์ และถูกเพิ่มเข้ามาในปี 2012
    • CVE-2026-39216 คือ heap buffer overflow ใน img2enc.c ที่เกิดจากการแทนที่ขนาด chroma ที่ปลอดภัยด้วยขนาดไม่จำกัดตามมิติ และถูกเพิ่มเข้ามาในปี 2012
    • CVE-2026-39217 คือ heap buffer overflow ใน VP9 decoder ที่เกิดจากฟังก์ชันอัปเดตขนาดที่ถูกรีแฟกเตอร์ ทำให้ tile thread buffer พลาดการ realloc ที่จำเป็น และเป็น regression ที่เข้ามาในเดือนมีนาคม 2025
    • CVE-2026-39218 คือ heap buffer overflow ใน DASH demuxer ที่ไม่ปฏิเสธค่า duration ติดลบ ทำให้ดัชนีของ fragment array กลายเป็นค่าติดลบ และถูกเพิ่มเข้ามาในปี 2017
  • ช่องโหว่ที่อ้างอิงด้วยรหัสติดตามภายใน

    • DFVULN-127 คือ heap buffer overflow ที่ av1_handle_packet() ของ RTP AV1 depacketizer ข้าม Temporal Delimiter OBU และเลื่อนตำแหน่งเอาต์พุตตาม obu_size แต่ไม่ได้จัดสรรพื้นที่ขนาดเดียวกัน ทำให้ OBU ถัดไปเขียนออกนอกขอบบัฟเฟอร์
    • DFVULN-126 คือ heap buffer overflow ที่ run_legacy_unscaled() ในโค้ด swscale graph จัดการการแปลง interlaced YUV420P→NV12 ผิดพลาด จนเขียนเกิน Y-plane ปลายทาง 576 ไบต์
    • DFVULN-125 คือ stack buffer overflow ที่ jpeg_create_header() ของ RTP JPEG depacketizer สร้างส่วน quantization-table ลงใน stack buffer 1024 ไบต์ และเมื่อ qtable_len >= 1024 ค่า AV_WB16 หลัง packet จะเขียนเกินท้ายไป 2 ไบต์
    • DFVULN-124 คือ heap buffer overflow ที่ istg_parse_tile_grid() ในเส้นทาง AVIF overlay ไม่ปฏิเสธการอ้างอิง dimg ที่มี tile entry เป็น 0 รายการ ทำให้หลัง unsigned wrap เกิดการอ่านนอกขอบเขตจาก heap allocation ขนาด 1 ไบต์
    • DFVULN-123 คือ integer overflow ที่ latm_parse_packet() ของ RTP LATM depacketizer ใช้ signed 32-bit addition overflow เพื่อหลบการตรวจขอบเขต ทำให้ memcpy อ่านเลยท้าย heap buffer ไปราว 1GB
    • DFVULN-122 คือ heap buffer overflow ที่ aac_parse_packet() ของ RTP MPEG-4 depacketizer ยอมรับ AU-headers-length เป็น 0 จนเกิดการจัดสรร 1 ไบต์ แล้วอ่านเป็นฟิลด์ 4 ไบต์โดยไม่ตรวจว่ามี AU header อยู่จริง
    • DFVULN-121 คือ heap buffer underflow ที่ read_seek() ของ CAF demuxer ไม่ตรวจค่าที่ av_index_search_timestamp() คืนมาเป็น -1 ก่อนใช้เป็นดัชนีอาร์เรย์ ทำให้เข้าถึง index_entries[-1]
    • DFVULN-120 คือ integer underflow ที่ ff_read_riff_info() ของ AVI demuxer รับ size - 4 โดยไม่ตรวจ size >= 4 ทำให้ LIST chunk ขนาด 0 underflow เป็นราว 4GB และนำไปสู่การจัดสรรราว 2GB
    • DFVULN-119 คือ heap buffer overflow ที่การเพิ่มค่าเกินจำเป็นใน opt_map() ของ option parser ทำให้ parse link-label เป็น file index ผิดพลาด และเก็บ stream index -1 จนลูปภายหลังอ่านก่อนหน้าอาร์เรย์ AVStream**
    • DFVULN-118 คือ heap buffer overflow ที่ rtsp_read_announce() ในเส้นทาง RTSP server จัดการ Content-Length ติดลบว่าใช้ได้ ทำให้ ANNOUNCE ระยะไกลพร้อม Content-Length: -1 เขียนออกนอกขอบเขตที่ sdp[-1]
    • DFVULN-117 คือ heap buffer overflow ที่ rtmp_calc_swfhash() ของ RTMP client ตรวจเพียง in_size < 3 แทน in_size < 8 ทำให้อ่าน 8 ไบต์จากบัฟเฟอร์ขั้นต่ำ 3 ไบต์
    • DFVULN-116 คือ heap buffer overflow ที่ sdp_parse_line() ในการพาร์ส RTSP SDP คำนวณ strlen(control_url) - 1 บนสตริงว่าง ทำให้ size_t wrap เป็น SIZE_MAX และเกิดการอ่าน pre-buffer 1 ไบต์

จากตัวมาร์กเกอร์ของเฟรมที่ถูกข้ามไปสู่การควบคุม PC

  • ในบรรดารายการที่ค้นพบทั้ง 21 รายการ มี heap buffer overflow ใน AV1 RTP depacketizer ที่เข้าถึงได้ผ่านเครือข่ายโดยไม่ต้องใช้แฟลกพิเศษใด ๆ
  • เหยื่อเพียงแค่รัน ffmpeg -i rtsp://attacker/stream และ packet เดียวขนาด 183 ไบต์ ก็สามารถเปลี่ยนทิศทางการทำงานได้
  • เมื่อ FFmpeg ดึง RTSP stream เซิร์ฟเวอร์จะส่งวิดีโอที่เข้ารหัสมาเป็นลำดับของ RTP packet
  • AV1 จัดบิตสตรีมเป็น OBU (Open Bitstream Units) และ RTP payload format จะแบ่ง OBU เหล่านี้ออกเป็นหลาย packet
  • depacketizer ของ FFmpeg ทำหน้าที่นำ OBU ที่ถูกแบ่งกลับมาต่อเป็น elementary stream ที่สะอาดอีกครั้ง
  • Temporal Delimiter (TD) เป็นมาร์กเกอร์ขนาดเล็กที่คั่นหนึ่ง temporal unit หรือก็คือระหว่างเฟรมหนึ่งกับเฟรมถัดไป
  • สเปกระบุว่า depacketizer ต้อง “ignore and remove” TD ที่อยู่ใน payload
  • กระบวนการ “ignore and remove” นี้เองกลายเป็นจุดที่มีปัญหา และเอเจนต์ตรวจพบจุดดังกล่าว

สาเหตุราก

  • depacketizer สร้าง output packet แบบค่อยเป็นค่อยไป โดยมีเคอร์เซอร์ pktpos ติดตามตำแหน่งไบต์ถัดไปที่จะเขียนใน pkt->data
  • pktpos เริ่มต้นจากท้ายปัจจุบันของ packet
// libavformat/rtpdec_av1.c:199
pktpos = pkt->size;
  • เมื่อโค้ดวนผ่าน OBU element ใน packet ทุกไบต์ที่มีการส่งออกจริงจะมีการเรียก av_grow_packet นำหน้า ซึ่งจะขยาย heap allocation ของ pkt->data
  • invariant ที่ทั้งรูทีนนี้พึ่งพาคือ pktpos ต้องไม่วิ่งนำหน้าขนาดที่จัดสรรไว้ของ pkt->data
  • โค้ดจัดการ Temporal Delimiter ทำลาย invariant นี้
// libavformat/rtpdec_av1.c:250
if ((obu_type == AV1_OBU_TEMPORAL_DELIMITER) ||
    (obu_type == AV1_OBU_TILE_LIST)) {
    pktpos += obu_size;
    rem_pkt_size -= obu_size;
    obu_cnt++;
    continue;
}
  • เมื่อข้าม TD ค่า pktpos จะถูกเลื่อนไปข้างหน้าตาม obu_size ที่ผู้โจมตีประกาศไว้ แต่ไม่ได้มีการจัดสรรหน่วยความจำมารองรับการเลื่อนนั้น
  • ตัวชี้อินพุต buf_ptr ก็ไม่ได้เลื่อนผ่านไบต์ของ TD ด้วย
  • หาก TD มี obu_size = 148 ค่า pktpos จะกลายเป็น 148 แต่ pkt->data อาจยังไม่ได้ถูกจัดสรรเลยหรือยังเล็กกว่า 148 ไบต์มาก
  • การวนรอบถัดไปจะพาร์สไบต์ของ TD เดิมซ้ำอีกครั้ง อ่านไบต์ header ของ TD ใหม่เป็นความยาว OBU ใหม่ และใช้ payload เป็นเนื้อหาของ OBU ที่ถูกปรับแต่ง
  • เมื่อเจอ OBU ปกติถัดไป packet จะถูกขยายเพิ่มเพียงเท่าขนาดของ OBU นั้น
// libavformat/rtpdec_av1.c:296
if ((result = av_grow_packet(pkt, output_size)) < 0)
    return result;

// libavformat/rtpdec_av1.c:304 / 336
pkt->data[pktpos++] = *buf_ptr++ | AV1F_OBU_HAS_SIZE_FIELD;
memcpy(pkt->data + pktpos, buf_ptr, obu_payload_size);
  • หาก OBU ที่ถูกปรับแต่งมีขนาด 17 ไบต์ av_grow_packet จะจัดสรรบัฟเฟอร์ 81 ไบต์ โดยรวม 17 ไบต์กับ input padding 64 ไบต์ของ FFmpeg
  • การเขียนจะเริ่มที่ pkt->data[148] ซึ่งอยู่เลยท้ายการจัดสรรไป 67 ไบต์
  • ข้อบกพร่องนี้จึงกลายเป็น heap buffer overflow ที่ผู้โจมตีควบคุมได้ทั้งออฟเซ็ตและเนื้อหาที่เขียนทับ

วิธีการโจมตี

  • เพื่อให้ overflow ที่ควบคุมได้มีประโยชน์ จะต้องมีเป้าหมายที่เขียนทับได้อยู่ถัดจากบัฟเฟอร์ และ allocator ของ FFmpeg ก็จัดวางเป้าหมายดังกล่าวไว้พอดี
  • เมื่อ av_grow_packet จัดสรร packet data buffer มันจะผ่าน av_buffer_alloc ซึ่งฟังก์ชันนี้จะจัดสรร data buffer, โครงสร้าง bookkeeping AVBuffer และ AVBufferRef ต่อกันบน heap
  • FFmpeg ใช้ posix_memalign แบบจัดแนว 64 ไบต์ ดังนั้น data buffer ขนาด 81 ไบต์จะครอบครอง chunk ขนาด 128 ไบต์ และ struct AVBuffer จะถูกวางถัดไปทันที
  • ใน struct AVBuffer มี function pointer ที่ใช้เป็น free callback
// libavutil/buffer_internal.h
struct AVBuffer {
    uint8_t *data;        // +0
    size_t   size;        // +8
    atomic_uint refcount; // +16
    void (*free)(void *opaque, uint8_t *data); // +24
    void    *opaque;      // +32
    ...
};
  • เมื่ออิงจากจุดเริ่มต้นของ data buffer ตัวชี้ AVBuffer.free อยู่ที่ออฟเซ็ต 152
  • หาก TD มี obu_size = 148 การเขียนจะเริ่มที่ pkt->data[148]
  • ไบต์ header ของ TD คือ 0x10 จะถูกตีความใหม่เป็นความยาว 16 และ header กับ payload ของ OBU ขนาด 16 ไบต์ที่ถูกปรับแต่งจะถูกเขียนตั้งแต่ออฟเซ็ต 148
  • AVBuffer.refcount อยู่ที่ออฟเซ็ต 144–147 จึงยังคงอยู่ก่อนตำแหน่งเริ่มเขียนที่ 148 และรักษาค่าเดิม 1 ไว้
  • เอ็กซ์พลอยต์ฝัง OBU ที่ถูกปรับแต่งตัวที่สามไว้ใน TD payload เพื่อบังคับให้เกิด av_grow_packet อีกครั้ง
  • เนื่องจากบัฟเฟอร์ถูกสร้างด้วย av_buffer_alloc ไม่ใช่ av_buffer_realloc มันจึงไม่ถูกทำเครื่องหมายว่า reallocatable และ FFmpeg จะเลือกเส้นทางที่จัดสรรบัฟเฟอร์ใหม่ก่อน แล้วค่อยปล่อยบัฟเฟอร์เก่า
// libavutil/buffer.c:209
if (!(buf->buffer->flags_internal & BUFFER_FLAG_REALLOCATABLE) || ...) {
    ret = av_buffer_realloc(&new, size);
    memcpy(new->data, buf->data, ...);
    buffer_replace(pbuf, &new);
    return 0;
}
  • buffer_replace จะลด refcount ของบัฟเฟอร์เดิมจาก 1 เป็น 0 แล้วเรียก free callback
// libavutil/buffer.c:129
if (atomic_fetch_sub_explicit(&b->refcount, 1, memory_order_acq_rel) == 1) {
    b->free(b->opaque, b->data);
}
  • ณ จุดนี้ free pointer ที่ถูกเขียนทับจะถูกเรียกใช้ และทำให้ควบคุม instruction pointer ได้
  • ใน release build RTP packet ขนาด 183 ไบต์เพียง packet เดียวทำให้ rip กลายเป็น 0xdeadbeef
#0  0x00000000deadbeef in ?? ()
rip            0xdeadbeef          0xdeadbeef
#1  buffer_replace (buffer.c:133)
#2  av_buffer_realloc (buffer.c:220)
#3  av_grow_packet (packet.c:151)
#4  av1_handle_packet (rtpdec_av1.c:296)
#5  rtp_parse_packet_internal (rtpdec.c:743)

ขอบเขตผลกระทบและ PoC

  • สภาพแวดล้อมที่ทำให้ FFmpeg เปิด RTSP URL ที่ผู้โจมตีมีอิทธิพลได้ จะได้รับผลกระทบจากช่องโหว่นี้
  • media ingest pipeline ที่ดึง stream URL จากผู้ใช้ อยู่ในขอบเขตผลกระทบ
  • ระบบ surveillance และ CCTV ที่ดึง RTSP feed อยู่ในขอบเขตผลกระทบ
  • transcoding service ที่ประมวลผลแหล่ง AV1-over-RTP ระยะไกล อยู่ในขอบเขตผลกระทบ
  • ไม่ต้องมีการยืนยันตัวตนหรือการโต้ตอบจากผู้ใช้นอกเหนือจากการเปิด stream และไม่ต้องใช้ command-line flag แปลก ๆ
  • ช่องโหว่นี้ถูกกระตุ้นในขั้นตอน RTSP PLAY ปกติที่ไคลเอนต์เหล่านี้ทำอยู่แล้วตามการออกแบบ
  • โค้ด PoC อยู่ที่ ffmpeg-dfvuln127

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

 
GN⁺ 3 시간 전
ความคิดเห็นจาก Hacker News
  • FFmpeg มีประวัติด้านความปลอดภัยที่แย่ผิดปกติ
    เป็นเวลานานมาแล้วที่ทุกครั้งซึ่งรัน fuzzer ก็มักเจอบั๊ก memory corruption โผล่มาแทบไม่รู้จบ และเมื่อ 10 ปีก่อนก็เคยมีงานจากพนักงาน Google ด้วย: https://security.googleblog.com/2014/01/ffmpeg-and-thousand-...
    ดังนั้นแม้นี่จะเป็นเดโมที่แสดงความสามารถของ LLM แต่ก็ไม่ใช่เรื่องน่าแปลกใจนัก ถ้าคุณกำลังจัดการคอนเทนต์ที่ไม่น่าเชื่อถือหรือผู้ใช้เป็นคนส่งมา ก็ไม่ควร รัน FFmpeg นอก sandbox และการทำแบบนั้นคือการยอมรับความเสี่ยงที่ไม่สมเหตุสมผล

    • ฝั่ง FFmpeg บ่นมาโดยตลอดว่ามีคนจำนวนมากมากที่อยากเปิดเผยช่องโหว่ของโปรเจกต์ แต่มีคนน้อยกว่ากันแบบเทียบไม่ติดที่จะลงมือทำ patch เพื่อแก้ไข
    • ไม่นานมานี้นักพัฒนา FFmpeg ไปออกรายการพอดแคสต์ของ Lex Fridman และก็มีการพูดถึงเรื่องความปลอดภัย
      มีช่องโหว่อยู่ใน codec เฉพาะทางมาก ๆ ที่เหมือนจะมีใช้แค่ในวิดีโอเกมยุค 90 เกมหนึ่งเท่านั้น ผู้รายงานทำเหมือนเป็นเรื่องใหญ่ แต่พวกเขาพูดทำนองว่ามันไม่ใช่เรื่องสำคัญเพราะเป็น codec ที่แทบไม่มีใครใช้
      แต่ก็อดสงสัยไม่ได้ว่าพวกเขาไม่รู้หรืออย่างไรว่า ถ้าผู้โจมตีสามารถส่งไฟล์วิดีโอมาได้ ก็เลือก video codec อะไรก็ได้ตามใจชอบ ต่อให้นักพัฒนาคิดว่า codec นั้นไม่มีใครใช้เลย ถ้ามันยังใช้งานได้ ผู้โจมตีก็ยังใช้มันได้
      เลยสงสัยว่าตัวเองพลาดอะไรไปหรือไม่ หรือมีเหตุผลที่สมควรจริง ๆ ที่ทำให้ช่องโหว่ของ codec นี้ไม่ใช่เรื่องใหญ่
    • แน่นอน เพราะทุกคนรู้จัก ทางเลือกที่ชัดเจน ที่จะใช้แทน FFmpeg อยู่แล้ว
    • การทำ sandbox ให้ FFmpeg เองไม่ใช่เรื่องยาก แต่กรณีอย่าง MPV/VLC ที่ใช้ FFmpeg จะยุ่งยากกว่า
      จนกระทั่งไม่นานมานี้ยังไม่มี virtio GPU native context จึงแทบเป็นไปไม่ได้เลยที่จะ sandbox วิดีโอเพลเยอร์โดยไม่เสีย hardware acceleration ทั้งหมด อย่างน้อยถ้าจะบังคับจากภายนอกก็ทำได้ยาก ส่วนภายในนั้นสามารถแยก FFmpeg ออกมาและล็อกด้วย seccomp อย่างเข้มงวดแบบ Chromium ได้
    • FFmpeg ทำงานอยู่ในโดเมนที่ซับซ้อนอย่างยิ่ง และเป็นซอฟต์แวร์ที่ซับซ้อนมากซึ่งต้องให้ความสำคัญกับประสิทธิภาพแบบสุดขีด
      นี่ไม่ใช่ปัญหาเฉพาะของ FFmpeg เท่านั้น Apple เองก็มีช่องโหว่ในตัวถอดรหัสภาพและวิดีโอนับไม่ถ้วน สาขานี้มันยากมากโดยตัวมันเอง และ FFmpeg ก็ทำงานได้ครอบคลุมกว่าทุกเจ้า
  • ผมคิดว่าอุตสาหกรรมนี้กำลัง optimize ผิดจุด มันง่ายมากที่จะใช้ของอย่าง Mythos preview 1 หรือ GPT-5.5 สร้างบั๊กรายงานที่ AI เขียนขึ้นมาหลายพันรายการ สิ่งที่ยากคือ การแก้บั๊กจริง ๆ
    ช่วงไม่กี่เดือนมานี้ผมกำลังสร้างระบบที่เปิด PR แทนการแค่หาประเด็นความปลอดภัยร้ายแรงแล้วส่งรายงานเข้ามา จนถึงตอนนี้อัตราการยอมรับอยู่ที่ประมาณ 94% สาเหตุที่ไม่ผ่านส่วนใหญ่ไม่ใช่เพราะวิเคราะห์ช่องโหว่ผิด แต่เป็นเพราะ kill switch เฉพาะโปรเจกต์หรือกลไกภายในที่ไม่มีการทำเอกสารไว้
    ดูเหมือนนักพัฒนาส่วนใหญ่จะชอบวิธีนี้มากกว่า รายงานบั๊กคือการสร้างงานเพิ่ม ส่วน PR ที่ดีคือการลดงานลง ฟังดูชัดเจนอยู่แล้ว แต่สินค้าด้านความปลอดภัยจำนวนมากก็ยังหยุดอยู่แค่การส่งรายงานแล้วจบ

    • ในความเป็นจริง อุตสาหกรรมนี้ optimize ที่ความเร็ว เวลาออกสู่ตลาด และฟีเจอร์ และใช้โมเดลแบบเอาหัวมุดทรายกับสิ่งที่ไม่สร้างรายได้ระยะสั้นอย่าง ประเด็นด้านความปลอดภัย, accessibility, vendor lock-in, interoperability และอื่น ๆ
      มันเป็นแบบนี้มาตลอดตั้งแต่อุตสาหกรรมนี้ถือกำเนิดขึ้น และตอนนี้เพิ่งเริ่มมีเครื่องมือที่เหมาะสมสำหรับประเมินความเสียหายและความเปราะบางโดยรวม
    • เหมือนมีบางอย่างตกหล่นตรงนี้ ซอฟต์แวร์ของ Apple ไม่มีโค้ดโอเพนซอร์ส แล้วจะเสนอการแก้ไขได้อย่างไร ผมไม่เข้าใจ
  • บั๊กนี้ร้ายแรงเพราะระยะการเข้าถึง ทุก deployment ที่ให้ FFmpeg ไปแตะ RTSP URL ที่อยู่ภายใต้อิทธิพลของผู้โจมตีล้วนเสี่ยง
    ไม่ว่าจะเป็นมีเดียอินเจสต์ไปป์ไลน์ที่รับ stream URL จากผู้ใช้ ระบบเฝ้าระวังหรือ CCTV ที่ดึง RTSP feed หรือบริการทรานส์โค้ดที่ประมวลผลแหล่ง AV1-over-RTP จากระยะไกล ทั้งหมดนี้เข้าข่าย มันร้ายแรงพอตัวจริง ๆ และถึงขั้นน่าแปลกใจที่มีการเปิดเผยออกมา ผมนึกบริการหลายอย่างที่ดูเหมือนถูกนำไปใช้โจมตีได้ทันทีออกเลย

    • หากรู้ว่ามีช่องโหว่ร้ายแรง การเปิดเผยก็อาจถือว่าสำคัญ เพราะคนที่ใช้งานซอฟต์แวร์ในรูปแบบที่เปราะบางจะได้ดำเนินมาตรการลดความเสี่ยงได้
    • กว่าจะไปถึงการโจมตีใช้งานจริงยังต้องมีอย่างอื่นเพิ่ม เช่น ASLR leak
    • FFmpeg บอกมาหลายครั้งแล้วว่าพวกเขาไม่ใส่ใจกับรายงานบั๊กหรือความปลอดภัย
  • ต่อให้เรื่องนี้จะดูเหมือนโฆษณาของบริษัทความปลอดภัย และอาจไม่ได้ใหญ่โตอย่างที่ทำให้ดู ก็ยังเตือนให้เห็นว่าที่ไหนสักแห่งในทุกแอปพลิเคชันที่คุณปล่อยออกมา ย่อมมีช่องโหว่ความปลอดภัยอยู่ และตอนนี้แม้แต่ script kiddie ก็ใช้ เครดิต 2 ดอลลาร์ หาเจอได้ภายใน 5 นาทีหลังปล่อย
    ถ้าคุณไม่ให้ red team ตรวจโค้ดก่อนปล่อย แฮกเกอร์ก็จะทำแทนหลังปล่อย

  • คำว่า zero-day กำลังถูกใช้เกินจริง ช่องโหว่ที่อธิบายไว้ไม่มีอันไหนเป็น zero-day จริง แต่พอเรียกแบบนั้นมันก็ดูเท่และเรียกคลิกได้ดี

  • ประโยคที่ว่า “ณ จุดนี้ free pointer ที่เสียหายถูกเรียกใช้ และสิทธิ์ควบคุม instruction pointer ก็ตกมาอยู่ในมือเรา” นั้นร้ายแรงมาก
    แต่อย่างไรก็ดี ดูเหมือนในทางปฏิบัติบั๊กนี้เพียงอย่างเดียวจะยังไม่พาไปถึง remote code execution ตามอำเภอใจได้ โดยเฉพาะเมื่อมี ASLR และยังต้องมีหน้าเมมโมรีที่ทั้งเขียนได้และรันได้อยู่ที่ไหนสักแห่งด้วย

    • ในบทความข้ามส่วนนี้ไปแบบลวก ๆ แต่ดูเหมือนว่าตัวแปรถัดไปใน struct จะบังเอิญเป็นอาร์กิวเมนต์ตัวแรกของฟังก์ชัน จึงมีทางเป็นไปได้ที่จะทำ arbitrary code execution ผ่านอะไรอย่าง system()
      ถึงอย่างนั้นก็ยังต้องมี exploit อื่นมาใช้เพื่อเจาะ ASLR อยู่ดี
  • นั่นไม่ใช่ความหมายของคำว่า “zero-day”

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

  • ฉันเองก็ใช้ FFmpeg มานานมาก ทั้งส่วนตัวและในบริการที่ฉันสร้างขึ้นมา Fabrice Bellard คืออัจฉริยะ และนักพัฒนาที่พา FFmpeg มาถึงจุดนี้ก็ทำให้โลกอุดมสมบูรณ์ขึ้นอย่างวัดได้
    แต่เมื่อรันกับอินพุตที่เชื่อถือไม่ได้ ก็แทบนึกไม่ออกเลยว่าจะมีโปรแกรมไหนที่ควรค่าแก่การ sandbox มากไปกว่า FFmpeg เพราะมันคือโค้ด C ขนาดมหึมาที่ต้องจัดการกับวิดีโอ·ออดิโอ codec ที่ซับซ้อนและขึ้นชื่อว่าเป็นเรื่องยากมากที่จะทำให้ถูกต้องสมบูรณ์
    ถึงอย่างนั้น ในทางปฏิบัติมันก็ไม่ได้เป็นปัญหาใหญ่นัก รัน FFmpeg ใน VM หรือ gVisor ได้อยู่แล้ว และผลลัพธ์สุดท้ายก็มักเป็นไฟล์วิดีโอที่คุณตั้งใจจะเล่นในเบราว์เซอร์อยู่ดี ซึ่งในเบราว์เซอร์ก็จะถูกถอดรหัสอีกทีภายใน sandbox อีกชั้นหนึ่ง ดังนั้นนี่จึงเป็นงานที่ยากมาตั้งแต่ต้น

    • มีลางสังหรณ์ที่หดหู่ว่าบรรดา บริษัทผู้ถือครองลิขสิทธิ์ ที่ต้องการ DRM, “แพลตฟอร์มที่เชื่อถือได้”, การครอบงำเชิงกำกับดูแล ฯลฯ น่าจะขยายความเสียหายบางส่วนจากเรื่องนี้
      การทำ sandbox ที่ปลอดภัยมักกลายเป็นโอกาสให้เกิดการคัดลอกแบบไม่จำกัดได้ง่าย
    • ไม่แน่ใจว่าคำว่า “ไฟล์วิดีโอที่คุณตั้งใจจะเล่นในเบราว์เซอร์” หมายถึงอะไร ถ้าสมมติว่าไฟล์วิดีโอใด ๆ ก็ไม่สามารถหนีออกจาก sandbox สำหรับการถอดรหัสของเบราว์เซอร์ ได้ แบบนั้นก็ปลอดภัยอยู่แล้วไม่ใช่หรือ?
    • แต่การเข้ารหัสก็ยังมีหลายกรณีที่ต้องใช้ hardware accelerator อยู่ดี สุดท้ายจึงมักต้องกลับไปใช้ C อีก
  • เป็นช่องโหว่ที่ในโค้ด reverse packetization ของ RTP LATM นั้น latm_parse_packet() ทำการบวก signed 32-bit แล้วเกิด overflow จนข้ามการตรวจสอบขอบเขตได้
    เป็นช่องโหว่อีกครั้งที่เกิดจากการบวกโดยไม่มีการตรวจสอบ และถึงอย่างนั้นภาษาแบบสมัยใหม่อย่าง Rust หรือ Go ก็ไม่ได้โยนข้อยกเว้นเมื่อ overflow ขณะที่สถาปัตยกรรม CPU สมัยใหม่อย่าง RISC-V ก็ไม่ได้มี overflow trap ให้ ส่วนภาษาเก่าอย่าง C หรือ C++ ก็แน่นอนว่าไม่มีการตรวจสอบ overflow เช่นกัน
    มันน่าขันดี เห็นได้ชัดว่าเราไม่อาจเชื่อได้ว่ามนุษย์จะเขียนโค้ดเลขคณิตได้ถูกต้องเสมอ

    • Rust เปิด การตรวจสอบ overflow ในโหมดดีบัก และสามารถเปิดได้ในโหมดรีลีสด้วย ซึ่งก็มีการใช้งานแบบนั้นจริง
      พฤติกรรม overflow ของจำนวนเต็มในโหมดรีลีสของ Rust ก็ถูกกำหนดไว้ชัดเจนเช่นกัน คือมันจะ wrap เท่านั้น จึงมีโอกาสนำไปสู่ช่องโหว่น้อยลง แน่นอนว่าถ้าเริ่มใช้ unsafe Rust ก็เป็นอีกเรื่อง
    • Zig จะโยนข้อยกเว้นเมื่อ overflow ส่วนการบวกแบบ saturating และ wrapping ใช้โอเปอเรเตอร์ +|= และ +%=
      Rust ไม่ได้โยนข้อยกเว้นจาก overflow เป็นค่าเริ่มต้น แต่ก็เขียนแบบ 123.checked_add(321) ได้ แบบนั้นโค้ดจะอ่านยากขึ้น แต่ปลอดภัยจาก overflow
      พูดตามตรง จากวิธีที่ฉันเขียนโค้ด ฉันว่ารูปแบบอย่างคอมเมนต์ท้ายบรรทัดน่าจะดีกว่า เช่น var x = y + z; # wrapped
      โอกาสที่จะผสมเลขคณิตแบบ wrapping·checked·saturating หลายแบบในบรรทัดเดียวมีน้อยมาก ใน Zig ทุกบรรทัดต้องสามารถคอมไพล์ได้ด้วยตัวเองโดยไม่ต้องอาศัยบริบทโค้ดอื่น ดังนั้นสถานะของคอมไพเลอร์แบบ doing(wrapped) { x + y } จึงใช้ไม่ได้ ชื่อฟังก์ชันก็ยาวเกินไป และการแปลงชนิดก็ยาวเกินไป ตัวแก้ไขระดับ statement อาจเป็นทางประนีประนอมที่ดี