1 điểm bởi GN⁺ 3 giờ trước | 1 bình luận | Chia sẻ qua WhatsApp
  • 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-suspend và 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

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 luksSuspend gầ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ể

    • Tôi nhắm đến một tiêu đề chính xác về mặt kỹ thuật chứ không cố kéo click
      Đú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 đó
    • Tôi không hiểu vì sao lại nói đây là chuyện chỉ riêng Debian. luksSuspend là tính năng upstream và đã được thêm vào từ bản phát hành v1.1.0 năm 2009
      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.9
      Tuy 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...
    • Lệnh con này có trong kho chính thức của cryptsetup và phần mô tả cũng có vẻ chính xác: https://gitlab.com/cryptsetup/cryptsetup/-/blob/main/man/cry...
    • Tôi đã dùng tính năng này trên Arch, và nó có thể dùng với LUKS nói chung. Chỉ là theo tôi biết thì nó không được dùng mặc định khi suspend
      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 định
    • Tôi thắc mắc Debian bắt đầu phát hành 6.9 từ phiên bản nào
  • Tô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

    • Đúng vậy. Trên hầu hết các bản phân phối Linux mặc định, nếu bạn chỉ suspend laptop thì mọi thứ, bao gồm cả khóa chính, vẫn còn trong bộ nhớ
      Nhưng Debian đã sớm tạo ra add-on tùy chọn cryptsetup-suspend; nó chạy lệnh luksSuspend, 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 passphrase
      Cho đế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
    • Các CPU Intel/AMD trong khoảng 5 năm trở lại đây đều hỗ trợ mã hóa toàn bộ bộ nhớ theo cách trong suốt với hệ điều hành
      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ớ

    • Rõ ràng là bản phân phối đó không dùng 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ỏ

    • Một phương án thỏa hiệp hợp lý là chỉ cần xóa LUKS header
      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
    • Nếu giả định khóa mã hóa đủ mạnh, thì về mặt lý thuyết việc xóa chỉ là thao tác dư thừa
  • 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?

    • Tôi thấy nhược điểm của C khá rõ và cũng không thường khuyên dùng cho dự án mới, nhưng tôi không nghĩ lỗi cụ thể này là ví dụ hay cho việc borrow checker của Rust hay hệ thống type của ngôn ngữ khác có thể bắt được. Có lẽ cả công cụ phân tích tĩnh cũng không bắt được
      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
    • Bản thân tiền đề về codebase công khai an toàn thì vẫn ổ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 đó
    • Kể cả dịch sang Rust thì chắc cũng chỉ thành “bỏ sót một dòng kiểm tra Rust” thôi
      Đâ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
    • Bài học rút ra ở đây là nếu một tính năng không có ít nhất test case liên quan, thì nó không phải tính năng thật sự
    • Lý do tiền đề “codebase C mã nguồn mở khổng lồ mà an toàn” có vẻ đáng ngờ là vì code review đôi khi chẳng khác mấy một bài toán dừng đã được lý tưởng hóa, nơi có thể truy cập vào một phiên bản hình thức hóa của đặc tả
      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

    • Đây là một hồi quy. Ứng dụng user-space có lẽ cũng đã âm thầm thất bại, và đây là kết quả của nhiều sự bất cẩn nối tiếp nhau
      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ộ

    • Đúng vậy. Đó là lý do kiểm thử tích hợp còn quan trọng hơn với các tính năng kiểu này
      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/532499
  • Trê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

    • Đồng ý. Hoặc cũng có thể hồi sinh FridgeLock: https://www.sec.in.tum.de/i20/publications/fridgelock-preven...
    • Nhưng lúc resume thì lấy khóa nào để giải mã bộ nhớ?
      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