
แอป Mac ของคุณอนุญาตให้ใช้ไมโครโฟน — แล้วลืมมันทุกครั้งที่เปิด
GeekBye ขอสิทธิ์ไมโครโฟน คุณกดอนุญาต และมันก็ทำงานได้ พอเปิดครั้งถัดไป: หายไป และแอปไม่เคยโผล่ใน System Settings → Microphone เลยด้วยซ้ำ ตัวการคือฟีเจอร์ความปลอดภัยของ macOS ที่แอบรันแอปจากพาธที่หายวับไป — นี่คือการวินิจฉัยและการแก้แบบกดครั้งเดียวจบ
นี่คือบั๊กที่ทำให้คุณสงสัยสายตาตัวเอง คุณติดตั้ง GeekBye มันขอสิทธิ์ไมโครโฟน คุณกด Allow แล้วการถอดเสียงก็ทำงาน เยี่ยม คุณปิดแอป เปิดมันใหม่ในเช้าวันถัดมา — แล้วมันก็ขอสิทธิ์ไมโครโฟนอีกครั้ง คุณเข้าไปที่ System Settings → Privacy & Security → Microphone เพื่อแก้ด้วยตัวเอง แต่ GeekBye ไม่มีอยู่ในรายการเลยด้วยซ้ำ ไม่ได้ถูกปฏิเสธ ไม่ได้ถูกอนุญาต แค่หายไปเฉยๆ ราวกับว่ามันไม่เคยขอ
ทุกชิ้นส่วนแยกกันดูถูกต้องหมด หน้าต่างขอสิทธิ์เป็นของจริง สิทธิ์ก็ทำงานได้ในตอนนั้น แอปถูกเซ็นและ notarize อย่างถูกต้องพร้อม usage string ที่ครบ กระนั้นการอนุญาตก็ระเหยหายไปทุกครั้งที่เปิด GeekBye v2.0.6 แก้มันแล้ว — และต้นตอคือหนึ่งในกลไกที่แนบเนียนที่สุดที่ macOS ทำเพื่อปกป้องคุณ
ฟีเจอร์ที่ซ่อนแอปจากตัวมันเอง
ตัวการคือ macOS App Translocation ฟีเจอร์ความปลอดภัยของ Gatekeeper เมื่อคุณดาวน์โหลดแอปแล้วรันมันตรงจาก DMG หรือจากโฟลเดอร์ ~/Downloads — ที่ไหนก็ตามที่มันยังถูก "quarantine" อยู่ — macOS ไม่ได้รันมันจากที่ที่คุณเห็นจริงๆ มันจะคัดลอกแอปแบบโปร่งใสไปยังพาธแบบสุ่มที่อ่านได้อย่างเดียวลึกอยู่ใต้ /private/var/folders/.../AppTranslocation/… แล้วรันสำเนาตัวนั้น นี่เป็นการป้องกันที่ดี: มันหยุดไม่ให้ไฟล์ที่ดาวน์โหลดมาแบบประสงค์ร้ายไปยุ่งกับไฟล์ที่อยู่ข้างๆ ตัวมันเอง
แต่นี่คือจุดที่ชนกัน ระบบสิทธิ์ของ macOS (TCC — ตัวที่คอยติดตามว่าใครใช้ไมค์ กล้อง หน้าจอของคุณได้) ระบุตัวแอป จากพาธและตัวตนของโค้ด (code identity) เมื่อแอปถูก translocate พาธนั้นจะเป็นแบบสุ่มและชั่วคราว ดังนั้นเมื่อคุณอนุญาตให้ใช้ไมโครโฟน macOS ก็บันทึกการอนุญาตอย่างซื่อสัตย์ — กับพาธที่จะไม่มีอยู่แล้วในการเปิดครั้งถัดไป พอเปิดแอปใหม่ macOS ก็ translocate มันไปยังพาธสุ่มที่ต่างออกไป เห็นแอปที่มันไม่มีบันทึกอยู่เลย จึงถามอีกครั้ง และเพราะพาธผีตัวนั้นไม่เคยเป็นตำแหน่งที่คงที่ แอปจึงไม่เคยได้บรรทัดถาวรใน System Settings → Microphone
แอปกำลังอนุญาตสิทธิ์ให้กับผี
นี่ก็เป็นเหตุผลว่าทำไมมีแค่บางคนที่เจอ ถ้าสำเนา GeekBye ของคุณอยู่ใน /Applications อยู่แล้ว — เพราะคุณลากมันไปไว้ที่นั่น หรือเพราะมันไปอยู่ที่นั่นผ่านการอัปเดตอัตโนมัติ — ก็ไม่มี quarantine ไม่มี translocation มีพาธที่คงที่ และทุกอย่างถูกจดจำอย่างสมบูรณ์แบบ บั๊กนี้จึงมองไม่เห็นทั้งสำหรับเราและสำหรับใครก็ตามที่ผ่านการติดตั้งครั้งแรกไปแล้ว ซึ่งเป็นบั๊กประเภทที่อยู่รอดได้นานที่สุดพอดี
การแก้: มอบบ้านจริงให้แอป
เพราะปัญหาทั้งหมดคือพาธที่ไม่คงที่ การแก้จึงคือการพาแอปไปอยู่บนพาธที่คงที่ v2.0.6 ตรวจจับเมื่อ GeekBye กำลังรันแบบ translocated (หรือแค่รันอยู่นอก /Applications) แล้วเสนอปุ่ม "Move to Applications" แบบคลิกเดียว — โดยใช้คำสั่งย้ายตำแหน่งของ macOS ที่คัดลอก bundle เข้าไปใน /Applications แล้วรีสตาร์ทแอปจากที่นั่น นับจากจุดนั้นเป็นต้นไป แอปก็มีตัวตนที่ตายตัว: สิทธิ์ไมโครโฟนถูกจดจำ สิทธิ์บันทึกหน้าจอถูกจดจำ และในที่สุด GeekBye ก็ปรากฏใน System Settings ในที่ที่คุณคาดหวัง
หน้าต่างขอสิทธิ์นั้นสุภาพ มันเสนอ Move to Applications, Not Now และ Don't Ask Again — และมันจดจำตัวเลือกล่าสุดเอาไว้ เพื่อไม่ให้แอปคอยรบกวนคนที่มีเหตุผลจงใจที่จะรันมันจากที่อื่น การตัดสินใจว่าควรจะแสดงหน้าต่างนี้ไหมถูกแยกออกมาเป็นฟังก์ชันเล็กๆ ที่บริสุทธิ์ (build นี้ถูก translocate ไหม? มันอยู่นอก /Applications ไหม? ผู้ใช้ปิดมันไปหรือเปล่า?) ตรรกะนี้จึงถูก unit-test ได้โดยไม่ต้องเปิด build ที่ notarize จริงบนวอลุ่มที่ถูก quarantine จริง
รีลีสเดียวกันนี้ยังทำให้ประสบการณ์การขอสิทธิ์เองเรียบง่ายขึ้นด้วย GeekBye เคยเด้งหน้าต่างขอสิทธิ์ในแอปแบบทำเองขึ้นมา — UI สองสามร้อยบรรทัดที่พยายามเลียนแบบสิ่งที่ OS ทำได้ดีอยู่แล้ว v2.0.6 ลบมันทิ้งและหันไปพึ่ง หน้าต่างขอสิทธิ์เนทีฟของ macOS โดยมีแบนเนอร์เงียบๆ ที่ไม่ขวางการทำงานคอยหนุน ซึ่งจะโผล่ขึ้นเฉพาะเมื่อสิทธิ์ที่จำเป็นขาดหายไปจริงๆ โค้ดน้อยลง และเป็นพฤติกรรมที่ผู้ใช้คุ้นเคยอยู่แล้วเพราะแอป Mac ตัวอื่นทุกตัวก็ทำงานแบบเดียวกัน
ส่วนที่ผมภูมิใจที่สุด: เราพิสูจน์มันก่อนที่จะเชื่อ
มันคงง่ายที่จะเดาว่าเป็น translocation แล้วปล่อยการแก้ออกไปเลย แต่แทนที่จะทำแบบนั้น รีลีสนี้ปล่อย เครื่องมือวินิจฉัยตอนเริ่มเปิดออกไปก่อน: ตอนเปิดแอป GeekBye จะรายงานพาธของไฟล์รันของตัวเองและว่ามันถูก translocate ไหม อยู่ใน /Applications ไหม และสถานะสิทธิ์ไมโครโฟนกับหน้าจอในปัจจุบัน เทเลเมทรีนั้นเปลี่ยนทฤษฎีที่ดูมีเหตุผลให้กลายเป็นทฤษฎีที่ยืนยันแล้ว — ข้อมูลจริงในโปรดักชันแสดงให้เห็นว่าเซสชันที่ได้รับผลกระทบนั้น อันที่จริงกำลังรันจากพาธที่ถูก translocate อยู่นอก /Applications ตรงตามที่ทำนายไว้เป๊ะ
ลำดับนี้สำคัญ "หน้าต่างขอสิทธิ์โผล่แต่สิทธิ์ไม่ถูกจดจำ" มีคำอธิบายที่เป็นไปได้หลายอย่าง — ปัญหาการเซ็น usage string ที่ขาด ปัญหา entitlement หรือความแปลกของฐานข้อมูล TCC เราตัดสาเหตุระดับ API ออกไป (เส้นทางการขอสิทธิ์นั้นถูกต้องอย่างพิสูจน์ได้) แล้วปล่อยให้ข้อมูลจากโลกจริงชี้ไปที่ชั้นตัวตน/พาธแทน แทนที่จะปล่อยการแก้ที่เต็มไปด้วยความหวังออกไปแล้วภาวนาให้ตั๋วซัพพอร์ตหยุด
สามสิ่งที่บั๊กนี้สอน
- สิทธิ์ที่เด้งขอแต่ไม่ถูกจดจำคือปัญหาเรื่องตัวตน ไม่ใช่ปัญหาเรื่องAPI ถ้าโค้ดการขอถูกต้องแล้วแต่การอนุญาตยังหายไป หยุดเขียนโค้ดการขอใหม่ ถามว่า OS กำลังผูกการอนุญาตไว้กับพาธและ code identity ตัวไหน — และตัวตนนั้นคงที่ข้ามการเปิดแต่ละครั้งไหม
- ปล่อยเครื่องมือวินิจฉัยไปพร้อม (หรือก่อน) การแก้ ฟิลด์ไม่กี่ตัว — ฉันกำลังรันจากที่ไหน สถานะสิทธิ์ของฉันเป็นอย่างไร — เปลี่ยนการเดาที่มีหลักการให้กลายเป็นต้นตอที่ยืนยันแล้ว และบอกเราได้ชัดว่าผู้ใช้กลุ่มไหนได้รับผลกระทบ ติดตั้งเครื่องมือวัดที่ขอบเขตที่คุณสงสัยก่อนที่จะแพตช์มัน
- บั๊กที่ซ่อนจากนักพัฒนาคือบั๊กที่รันอยู่ในสภาพแวดล้อมที่ "ผิด" ของเราอาศัยอยู่ใน
/Applicationsบนทุกเครื่องพัฒนา บั๊กจึงมองไม่เห็นเชิงโครงสร้างสำหรับเรา ในขณะที่มันเล่นงานผู้ใช้ครั้งแรก เมื่อรายงานหนึ่งทำซ้ำไม่ได้ คำถามแรกคืออะไรต่างกันเรื่องที่ที่มันรัน ไม่ใช่ผู้ใช้เข้าใจผิดหรือเปล่า
GeekBye v2.0.6 ปล่อยหน้าต่างย้ายตำแหน่งและเครื่องมือวินิจฉัยไปพร้อมกัน สำหรับส่วนโค้งเรื่องความน่าเชื่อถือที่ใหญ่กว่าซึ่งเรื่องนี้เป็นส่วนหนึ่ง ดูบันทึกว่าเวอร์ชัน 2 ต้องใช้อะไรจริงๆ (v2.0.0) และทำไมการบันทึกหน้าจอถึงจับมอนิเตอร์ผิดตัว (v2.0.10) — อีกบั๊กหนึ่งที่โผล่มาเฉพาะในสภาพแวดล้อมจำเพาะ สำหรับรีลีสเรื่องรายละเอียดเล็กๆ ข้างเคียง ดูซอฟต์แวร์ที่สงบนิ่ง: การแก้อาการกระพริบกับชิปโหมดตอบ (v2.0.3 + v2.0.5)