Từ Linux 6.9, LUKS suspend không thể xóa khóa mã hóa đĩa khỏi bộ nhớ
(mathstodon.xyz)- Từ Linux 6.9, công cụ khóa ổ đĩa khi laptop chuyển sang suspend đã âm thầm thất bại, khiến khóa mã hóa toàn bộ đĩa LUKS vẫn còn trong bộ nhớ
- Nguyên nhân là sự tương tác ngoài dự kiến giữa đợt tái cấu trúc truy cập thiết bị khối được đưa vào Linux 6.9 vào tháng 5/2024 và mã mã hóa; bản sửa được đề xuất là một bản vá chỉ một dòng
- Vấn đề không lộ ra ở trường hợp tắt máy hoàn toàn, nhưng với suspend-to-RAM thì khóa vẫn còn, khiến kẻ tấn công chiếm được một laptop đang bật nguồn có thể trích xuất khóa từ RAM
- Việc phát hiện bắt đầu khi dọn dẹp bản port NixOS của Debian
cryptsetup-suspendvà nhìn thấy mục trong/proc/keys; sau đó xác nhận bằng memory dump trên QEMU rằng volume key đáng lẽ phải bị xóa vẫn còn tồn tại - Đã có đề xuất kiểm thử tích hợp cho NixOS và bản vá cảnh báo cho cryptsetup; các tính năng bảo mật như xóa khóa ngay trước khi suspend có thể trông như vẫn hoạt động bình thường nhưng rất dễ bỏ sót lỗi nếu không xác minh bộ nhớ thực tế
Vấn đề khóa LUKS còn tồn tại trong lúc suspend từ Linux 6.9
- Kể từ Linux 6.9, tức từ tháng 5/2024, công cụ khóa ổ đĩa khi laptop chuyển sang suspend đã âm thầm thất bại
- Mã hóa toàn bộ đĩa LUKS được dùng để bảo vệ dữ liệu khi laptop bị thất lạc, tịch thu hoặc đánh cắp, nhưng trong trường hợp này khóa mã hóa vẫn còn trong bộ nhớ trong suốt thời gian suspend
- Khi tắt máy hoàn toàn thì vẫn hoạt động, nhưng tác động lớn vì nhiều người thường để máy ở trạng thái suspend-to-RAM thay vì tắt hẳn
- Nếu ai đó chiếm được laptop khi máy vẫn đang bật, khóa còn lại trong bộ nhớ có thể bị lộ
- VeraCrypt được nhắc đến như phần mềm có cùng mục đích trên Windows, nhưng trong phần bình luận sau đó có đính chính rằng “canonical software” không có nghĩa là phần mềm được dùng rộng rãi nhất mà là lựa chọn tiêu biểu được giới an toàn thông tin khuyến nghị
Nguyên nhân và bản vá một dòng
- Nguyên nhân là commit tái cấu trúc của nhân Linux md: port block device access to file
- Bản thân thay đổi này là một đợt tái cấu trúc hợp lý và hữu ích, nhưng đã tạo ra tương tác tầm xa với mã mã hóa
- Bản sửa được đề xuất là bản vá một dòng
- Tác giả bản vá nói rằng nếu không có kiểm chứng hình thức thì không thể khẳng định bản vá này là đúng và không tạo ra các tương tác tầm xa khác
- Cũng có thêm các công việc tiếp theo để ngăn tái diễn
- Kiểm thử tự động của NixOS: kiểm thử tích hợp để phát hiện hồi quy trong tương lai
- Merge request của cryptsetup: bản vá để in cảnh báo thay vì âm thầm thất bại
Quá trình phát hiện
- Điểm khởi đầu là công việc dọn dẹp bản port NixOS của Debian
cryptsetup-suspend - Cả bản gốc Debian lẫn bản port NixOS đều có một race condition gây phiền toái nhưng không gây hại, đôi khi khiến laptop không thể đi vào trạng thái ngủ
- Để giải quyết việc này, người viết đã cố khôi phục bản vá nhân chưa được hợp nhất của Pali Rohár là bản vá xóa khóa khi dm-crypt suspend/hibernation
- Trong quá trình đó, khi xem mã nguồn của cryptsetup và nhân, người viết xác nhận rằng theo tài liệu thì keyring được gắn với luồng gọi và sẽ bị xóa khi luồng kết thúc
- Tuy nhiên, các mục vẫn xuất hiện trong
/proc/keys, thứ mà trước đó người viết chưa biết đến, làm dấy lên nghi ngờ - Cuối cùng, một máy ảo QEMU được khởi chạy để dump bộ nhớ, và xác nhận rằng LUKS volume key đáng lẽ phải bị xóa vẫn còn nguyên
Dự án secure suspend-to-RAM cho NixOS
- Dự án
secure-suspendđược công bố riêng, cung cấp secure suspend-to-RAM ở dạng thử nghiệm cho NixOS - Mã hóa toàn bộ đĩa thông thường để lại khóa trong bộ nhớ khi laptop đang suspend, nên có thể dễ tổn thương trước cold boot attack hoặc các cách rò rỉ RAM
- Dự án này khôi phục bản vá nhân cũ của Pali Rohár để xóa khóa mã hóa LUKS khi suspend
- Dự án lấy cảm hứng từ Debian
cryptsetup-suspend, nhưng dùng bản vá nhân để tránh race condition đôi lúc khiến laptop không ngủ được, đồng thời bổ sung thêm biện pháp phòng ngừa - Dự án hỗ trợ đầy đủ root filesystem được mã hóa và cũng cung cấp kiểm thử tích hợp
- Bản vá nhân và công cụ user space có thể được điều chỉnh để dùng với các bản phân phối Linux khác
- Dự án được công bố tại secure-suspend
Vì sao khó kiểm chứng bảo mật của suspend
- Việc nhìn thấy màn hình khóa sau khi suspend không có nghĩa là thiết bị lưu trữ thực sự đã bị khóa
- Nếu có thể truy cập đĩa ngay sau khi tỉnh lại từ suspend, điều đó cho thấy thiết bị lưu trữ vốn chưa từng bị khóa ngay từ đầu
- Ví dụ, có thể dùng một script tiếp tục chạy phía sau màn hình khóa để kiểm tra truy cập đĩa
- Nếu sau khi suspend vẫn có thể đăng nhập bằng SSH public key mà không cần mở khóa thiết bị lưu trữ được mã hóa trước, thì rất dễ xác nhận rằng thiết bị chưa bị khóa
- Cũng có bình luận cho biết cấu hình mặc định của Ubuntu hay Debian vốn còn chưa hề cố gắng cung cấp kiểu bảo vệ này
- Việc cố gắng khóa thiết bị lưu trữ có thực sự hoạt động đúng hay không cần được kiểm chứng riêng
- Dấu thời gian trong log có thể được tạo trước khi suspend nhưng chỉ được ghi ra sau khi wake
- Ngược lại, log được tạo ngay sau khi wake trước khi đồng hồ hệ thống được điều chỉnh có thể trông như mang thời điểm của lúc suspend
- Việc khóa thiết bị lưu trữ diễn ra ngay trước khi suspend hay chỉ ngay sau khi resume có thể trông giống nhau về mặt trải nghiệm người dùng, nhưng về mặt bảo mật thì khác biệt mang tính quyết định
- Kiểm thử tích hợp của NixOS khởi động hệ thống trong máy ảo rồi dump bộ nhớ để xác nhận khóa có thực sự bị xóa khi suspend hay không
1 bình luận
Ý kiến trên Hacker News
Đúng là một lỗi thú vị, nhưng tiêu đề hơi có cảm giác như câu view
Theo cách tôi hiểu thì
cryptsetup luksSuspendgần giống một phần mở rộng do Debian tạo ra hơn là một tính năng được hỗ trợ chính thức, nên có lẽ bản hồi quy này cũng chỉ ảnh hưởng đến Debian thôi?Tôi không chắc có thể đổ lỗi cho kernel với một tính năng không được hỗ trợ hoặc kiểm thử rộng rãi như vậy
Dù sao thì nó vẫn đáng chú ý, và thật tốt khi đã có bài kiểm thử để lỗi hồi quy này không tái diễn. Tôi cũng đồng ý với OP rằng NixOSTests thực sự rất tuyệt
Chỉ là nếu nhìn tiêu đề thôi thì nó trông giống một vấn đề lan rộng chứ không phải chỉ của một bản phân phối cụ thể
Đúng vậy. Những người dùng thiết lập mặc định sẽ không bị ảnh hưởng, vì ngay từ đầu họ cũng sẽ không kỳ vọng khóa volume được an toàn trong lúc suspend
Cách giải quyết của Debian đã được port sang nhiều, có lẽ là hầu hết, các bản phân phối khác, và có lẽ cũng khá nhiều người tự duy trì bản port riêng
Trang man
thread-keyring(7)hứa rằng “thread keyring sẽ bị hủy khi luồng tham chiếu đến nó kết thúc”Dự án cryptsetup đã dựa vào thuộc tính này trong cơ chế đưa khóa từ user space lên kernel space, và kernel 6.9 đã đưa vào một bản hồi quy làm hỏng thuộc tính đó
Trước đây tôi cũng từng dùng nó đôi khi trên Arch và openSUSE, và nó chắc chắn có tồn tại trên các bản phân phối không phải Debian
Có lẽ bạn đang nghĩ đến việc tích hợp tự động với system suspend, nhưng đó không phải trọng tâm.
luksSuspendđược tài liệu hóa là sẽ xóa khóa khỏi bộ nhớ hệ thống, và hành vi đó đã ngừng hoạt động vì bản vá refactor này trong Linux 6.9Tuy vậy, trên thực tế cũng có thể xem đây là lỗi phía cryptsetup. Nó đã dựa vào hành vi vòng đời rất cụ thể của khóa keyring trong kernel, và cũng có thể lập luận rằng lẽ ra user space phải xóa nó một cách tường minh hơn
[1]: https://gitlab.com/cryptsetup/cryptsetup/-/commit/3cea5dcc7b...
[2]: https://gitlab.com/cryptsetup/cryptsetup/-/blob/main/docs/v1...
[3]: https://gitlab.com/cryptsetup/cryptsetup/-/merge_requests/93...
Có lẽ bạn đang nói tới một cơ chế thực sự thực hiện suspend RAM theo cách hữu ích sau
luksSuspend; cái đó ban đầu nhắm đến Debian và sau này cũng có trên Arch, nhưng cả hai đều không phải mặc địnhTôi không thấy còn cách nào khác hợp lý. Khi tiết kiệm điện, tức suspend RAM, mọi thứ đều được giữ trong RAM và ở trạng thái được mã hóa, nhưng theo tôi nhớ thì khóa chính vẫn còn trong bộ nhớ kernel
Còn khi hibernate, tức suspend xuống đĩa, toàn bộ nội dung RAM, bao gồm cả khóa chính, sẽ được ghi ra đĩa và mã hóa, còn RAM thì bị xóa
Khi đánh thức lại, bạn phải nhập lại passphrase để giải mã khóa chính rồi mới nạp lại nội dung từ đĩa vào bộ nhớ được
Nhưng Debian đã sớm tạo ra add-on tùy chọn
cryptsetup-suspend; nó chạy lệnhluksSuspend, vốn được thiết kế để xóa khóa khỏi bộ nhớ, rồi khi resume sẽ yêu cầu nhập lại passphraseCho đến kernel 6.8 thì nó hoạt động đúng như mô tả, nhưng начиная từ kernel 6.9 thì nó âm thầm ngừng hoạt động
Nếu bật tính năng này thì tấn công cold boot gần như trở thành chuyện quá khứ. Thường nó chỉ làm RAM chậm đi khoảng 0.5% nên mặc định bị tắt
Vì sau khi Sleep không phải nhập lại mật khẩu khởi động, nên rõ ràng khóa mã hóa vẫn còn trong bộ nhớ
cryptsetup-luksSuspendĐây không phải vấn đề khiến tôi quá bận tâm
Lý do duy nhất tôi dùng mã hóa đĩa là để khi bán laptop, tôi không phải lo ai đó lục lọi tài liệu thuế hay thông tin thẻ tín dụng
Tất nhiên tôi cũng xóa máy, nhưng nếu dữ liệu đã được mã hóa ở cấp độ ổ đĩa thì tôi cho rằng nguy cơ bị khôi phục bằng công cụ pháp chứng là cực nhỏ
LUKS dùng một thuật toán chống pháp chứng mà để mở đĩa thì phải có toàn bộ khóa volume. Nó kết hợp các khối khóa bằng thuật toán khuếch tán rồi XOR để tạo ra khóa chính thực sự, nên về lý thuyết chỉ cần xóa một sector của khóa volume là toàn bộ sẽ không thể khôi phục
Tức là chỉ cần thiếu một khối của khóa thì cũng không thể dễ dàng đoán ra phần còn lại
Tôi không phải chuyên gia bảo mật gì, nhưng dạo gần đây cứ thường xuyên phát hiện các lỗi bảo mật nghiêm trọng “xuất phát từ việc bỏ sót một dòng kiểm tra C khi đang refactor qua lại giữa nhiều file”, nên bản thân tiền đề về một codebase C mã nguồn mở khổng lồ mà an toàn đã thấy đáng ngờ
Không phải chỉ riêng C mới có vấn đề này, nhưng đặc biệt với C thì việc cưỡng chế và theo dõi các bất biến một cách nhất quán khó hơn, nhất là khi mã bị thay đổi
Tôi cũng không chắc lập trình hàm, nơi mã hóa bất biến vào type, có phải là lời giải thực tế có thể mở rộng hay không. Model checking? LLM fuzzing? Ít khối nền tảng hơn với ranh giới rõ ràng hơn? seLinux có được “kiểm chứng” theo kiểu đó không?
Về bản chất nó kiểu như thế này:
original:
DoTheThing()new:
DoTheThingSlightlyDifferentButKeepMyCredentialsAlive()fix:
DoTheThingSlightlyDifferentButDoInFactNOTKeepMyCredentialsAlive()Theo kinh nghiệm của tôi, khá nhiều lỗi khó chịu xuất phát từ việc vi phạm bất biến hệ thống ở cấp cao hơn, và những thứ như vậy có vẻ không phải loại dễ tự động hóa
Dùng Lean hay thứ tương tự thì cũng có thể chứng minh một chương trình thỏa mãn thuộc tính nào đó, nhưng trước tiên bạn phải nghĩ ra được thuộc tính đó đã. Chứng minh không tự phát hiện bất biến thay bạn
Nếu đã nghĩ ra thuộc tính bảo mật liên quan, thì viết kiểm thử hồi quy hẳn cũng không khó. Phần thật sự khó, theo tôi, không phải là biểu diễn cách cài đặt sao cho an toàn, mà là nhận ra rằng có một thuộc tính mà cách cài đặt phải bảo toàn
Vấn đề là khả năng được kiểm toán cao hơn không có nghĩa là tự động sẽ có nhiều kiểm toán hơn
Cần những người đủ giỏi dành đủ thời gian cho việc đó
Đây là lỗi phát sinh do các mối quan tâm giao cắt nhau và thiếu kiến thức liên miền. Trong Lisp hay assembly chắc cũng vậy thôi
Nói cách khác, không có định nghĩa nghiêm ngặt về việc cái gì cấu thành vấn đề bảo mật
Có phải cơ quan liên bang đang rất cần một cách để lấy khóa? Đây là bugdoor à? Đã lần theo commit chưa?
Gần đây tôi thấy kiểu mẫu này nhiều nên bắt đầu hơi nghi ngờ. Cũng có thể chỉ là vì mọi người nhạy hơn với chuyện này nên đăng nhiều hơn
Việc khóa mã hóa nằm trong bộ nhớ không đồng nghĩa là có thể trích xuất được ngay. Đúng hơn là nó bị giữ lại vô thời hạn một cách không cần thiết ở nơi lẽ ra không nên tồn tại
Những hồi quy như thế này rất dễ bị bỏ sót vì mọi thứ vẫn tiếp tục “hoạt động”. Lỗi bảo mật thường không tự bộc lộ
Viết nó cũng khá thú vị, và còn cho phép chạy
git-bisectđể tìm đúng đợt refactor kernel đã đưa lỗi này vào: https://github.com/NixOS/nixpkgs/pull/532499Trên laptop Fedora, tôi cấu hình Linux để sau 15 phút suspend thì hibernate xuống đĩa. Nếu cắt luôn điện cấp cho RAM thì lỗi riêng của Debian kiểu này không còn là vấn đề
Phần mở rộng công cụ Linux của Debian nghe thì hay trên lý thuyết, nhưng nếu thực sự lo về tấn công cold boot thì không chỉ khóa LUKS mà tất cả khóa và tài liệu quan trọng đều phải bị xóa khỏi bộ nhớ
Vì vậy cách đúng đắn để ngăn cold boot rốt cuộc vẫn là hibernate
Theo tôi biết thì nếu không dùng TPM sẽ không thực tế. Mà dùng TPM thì về cơ bản là phó mặc số phận cho TPM
Chỉ cần tưởng tượng xem nếu lỗ hổng này nằm trong một hệ điều hành thương mại thì thread HN này sẽ trông như thế nào
Comment top chắc chắn sẽ là kiểu Applosoft không còn quan tâm đến chất lượng phần mềm nữa, hoặc “đây là hậu quả khi để rác vibe coding chui vào OS”
Các comment bên dưới sẽ là thuyết âm mưu về tổ hợp công nghiệp giám sát và NSA, thứ ở nơi khác thì bị coi là điên rồ nhưng trên HN thì lại không
Tôi không hiểu vì sao thứ quan trọng thế này lại không được test ở mỗi bản build