Skip to content

Phân tích tĩnh mã nguồn

Trang này trình bày phân tích tĩnh (static analysis) cho mã C/C++ theo góc nhìn chất lượng mã và tuân thủ coding standard. Nội dung đi theo thứ tự: khái niệm và giới hạn, cách công cụ phân tích hoạt động, các nhóm công cụ, kiểm tra theo MISRA C, cách dùng trong quy trình phát triển và cách xử lý kết quả. Góc nhìn tìm lỗ hổng bảo mật bằng công cụ thương mại nằm ở trang Checkmarx và Klocwork (SAST).

Khái niệm

Phân tích tĩnh là kỹ thuật kiểm tra mã nguồn mà không chạy chương trình. Công cụ đọc mã (hoặc biểu diễn trung gian của mã), áp dụng một tập luật và báo các vị trí có khả năng sai. Kỹ thuật này bổ sung cho unit test và phân tích động (sanitizer, Valgrind): test chỉ chạy những đường đi mà test case chạm tới, còn phân tích tĩnh xét được cả đường đi hiếm như nhánh xử lý lỗi.

Các nhóm lỗi thường được phát hiện trong C/C++:

  • Truy cập ngoài biên mảng, tràn bộ đệm.
  • Dereference con trỏ NULL, dùng con trỏ sau khi giải phóng (use-after-free).
  • Rò rỉ bộ nhớ hoặc file descriptor.
  • Dùng biến chưa khởi tạo.
  • Tràn số nguyên và ép kiểu mất thông tin.
  • Vi phạm quy tắc đặt ra bởi coding standard (MISRA C, CERT C).

Giới hạn

Không công cụ nào vừa bắt được mọi lỗi vừa không báo sai, vì bài toán tổng quát là không quyết định được. Công cụ phải chọn một trong hai hướng, nên kết quả luôn có hai loại sai sót:

Loại Ý nghĩa Hệ quả
False positive Báo lỗi ở chỗ mã thực ra đúng Tốn thời gian triage, nhóm phát triển dần bỏ qua cảnh báo
False negative Không báo lỗi ở chỗ mã thực ra sai Tạo cảm giác an toàn không có căn cứ

Vì vậy kết quả phân tích tĩnh cần được xem xét bởi người, và công cụ không thay thế review mã hay test.

Cách công cụ phân tích

Các công cụ có độ sâu khác nhau. Mức nông chỉ so khớp mẫu, mức sâu theo dõi giá trị dữ liệu giữa các hàm.

Kỹ thuật Cách làm Ví dụ phát hiện được
So khớp mẫu trên cây cú pháp (AST) Tìm cấu trúc mã khớp với một luật Dùng gets(), strcpy() không kiểm tra độ dài
Phân tích luồng điều khiển (CFG) Dựng đồ thị các đường đi có thể có của hàm Mã không bao giờ chạy tới, thiếu return
Phân tích luồng dữ liệu (data flow) Theo dõi giá trị biến dọc theo đường đi Biến chưa khởi tạo, con trỏ có thể NULL
Symbolic execution Chạy chương trình với giá trị ký hiệu thay cho giá trị cụ thể, khám phá nhiều đường đi Chỉ số mảng có thể vượt biên với một điều kiện đầu vào nào đó
Phân tích liên hàm (inter-procedural) Mở rộng các kỹ thuật trên qua lời gọi hàm và qua nhiều file Con trỏ NULL được truyền qua ba hàm rồi mới bị dereference

Phân tích sâu tốn nhiều thời gian và bộ nhớ hơn, nên thường chạy ở máy chủ CI thay vì mỗi lần biên dịch.

Các nhóm công cụ

Nhóm Công cụ Đặc điểm
Cảnh báo của compiler gcc/clang với -Wall -Wextra, gcc -fanalyzer Có sẵn trong toolchain, không cần cài thêm
Mã nguồn mở độc lập cppcheck, clang-tidy, Clang Static Analyzer, sparse Miễn phí, dễ đưa vào CI, bộ luật gọn
Công cụ riêng của project checkpatch.pl (Linux kernel) Kiểm tra coding style theo quy ước của project
Thương mại Klocwork, Coverity, Checkmarx, Polyspace, Helix QAC Phân tích sâu liên hàm, bộ luật MISRA/CERT/CWE đầy đủ, dashboard và theo dõi theo release

Công cụ thương mại đáng cân nhắc khi sản phẩm phải chứng minh tuân thủ một tiêu chuẩn (MISRA C cho ô tô, các tiêu chuẩn an toàn chức năng), vì chúng thường kèm báo cáo tuân thủ theo từng luật.

Ví dụ dùng công cụ mã nguồn mở

Với compiler, bật cảnh báo và static analyzer của GCC:

gcc -Wall -Wextra -fanalyzer -c main.c

Với cppcheck, quét toàn bộ thư mục mã nguồn:

cppcheck --enable=warning,style,performance --inconclusive src/

Với clang-tidy, công cụ cần biết lệnh biên dịch của từng file. CMake sinh file này khi bật CMAKE_EXPORT_COMPILE_COMMANDS:

cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
clang-tidy -p build src/main.c

Klocwork

Klocwork là công cụ phân tích tĩnh thương mại của Perforce, hỗ trợ C, C++, C#, Java, JavaScript và Python. Với mã nhúng, điểm đáng chú ý là bộ checker theo MISRA C/C++ và CERT C/C++, nên thường được dùng để kiểm tra tuân thủ coding standard. Klocwork cần biết cách mã được biên dịch (include path, macro định nghĩa), nên bước đầu tiên là ghi lại quá trình build thay vì đọc file nguồn rời rạc. Quy trình triển khai và so sánh với Checkmarx xem Checkmarx và Klocwork (SAST).

Kiểm tra theo MISRA C

MISRA C là tập quy tắc lập trình C an toàn, xuất phát từ ngành ô tô và được dùng rộng rãi trong hệ thống nhúng có yêu cầu an toàn. Tổng quan các coding convention xem Coding Convention. Các điểm cần lưu ý khi dùng công cụ để kiểm tra MISRA:

  • Mỗi công cụ hiện thực một phần tập luật, và mức bao phủ khác nhau giữa các công cụ.
  • Một số luật không kiểm tra tự động được hoàn toàn, nên cần review thủ công.
  • Khi cần lệch khỏi một luật, cần ghi lại lý do (deviation) thay vì tắt luật trong cấu hình.

Todo

Chưa kiểm chứng bản MISRA C cụ thể (2012, 2023) và mức bao phủ luật của từng công cụ nêu trong trang. Cần đối chiếu với tài liệu của nhà cung cấp trước khi dùng làm căn cứ chứng nhận.

Tích hợp vào quy trình phát triển

Phân tích tĩnh có hiệu quả nhất khi chạy sớm và chạy tự động. Ba điểm tích hợp thường gặp:

  1. Trên máy lập trình viên: cảnh báo của compiler và plugin IDE, cho phản hồi trong vài giây.
  2. Trong pre-commit hoặc pre-push hook: các công cụ nhẹ như cppcheck, clang-tidy, checkpatch.pl.
  3. Trong pipeline CI: phân tích sâu trên toàn bộ project, chặn merge khi có lỗi mức cao. Vị trí của bước này trong pipeline xem CI/CD cho phần mềm nhúng.

Khi áp dụng cho codebase đã có sẵn, kết quả ban đầu thường rất nhiều. Cách thường dùng là đặt baseline: chấp nhận tồn đọng hiện tại và chỉ chặn lỗi mới phát sinh, sau đó giảm dần tồn đọng.

Xử lý kết quả

Mỗi cảnh báo cần được phân loại thành một trong ba trạng thái: lỗi thật cần sửa, false positive cần ghi nhận, hoặc chấp nhận có điều kiện kèm lý do. Với false positive, nên ghi chú ngay tại dòng mã bằng cơ chế che cảnh báo của công cụ, kèm lý do, để người đọc sau hiểu được. Không nên tắt hẳn một luật trên toàn project chỉ vì có vài false positive.

Liên quan

Tài liệu tham khảo