11 điểm bởi GN⁺ 2026-01-01 | 5 bình luận | Chia sẻ qua WhatsApp
  • Dự án cURL, sau khi trước đây đã loại bỏ strncpy(), nay cũng cấm hoàn toàn strcpy() trong toàn bộ codebase
  • strcpy() có API đơn giản nhưng có nguy cơ tách rời việc kiểm tra kích thước bộ đệm, nên không an toàn về lâu dài khi bảo trì
  • Để thay thế, một hàm mới curlx_strcopy() đã được đưa vào, nhận cả kích thước bộ đệm đích và độ dài chuỗi làm đối số để kiểm tra khả năng sao chép rồi mới thực hiện
  • Hàm này nội bộ dùng memcpy()đảm bảo cả việc xử lý ký tự kết thúc null
  • Thay đổi này giúp tăng tính bảo mật và tính nhất quán của mã nguồn, đồng thời cũng có thể giảm các báo cáo lỗ hổng sai do AI tạo ra

Bối cảnh loại bỏ strcpy

  • Trước đây cURL đã loại bỏ toàn bộ lời gọi strncpy(), đồng thời chỉ ra các vấn đề như API khó hiểu, không đảm bảo kết thúc null và việc thêm 0 đệm không cần thiết của hàm này
    • Với trường hợp cần sao chép chuỗi con, họ chuyển sang dùng memcpy() và tự xử lý việc kết thúc null
  • strcpy() có API đơn giản, nhưng vì không chỉ rõ kích thước bộ đệm nên trong quá trình bảo trì có thể xảy ra rủi ro tách biệt giữa mã kiểm tra và lời gọi sao chép
    • Khi mã nguồn bị nhiều lập trình viên chỉnh sửa suốt hàng chục năm, việc kiểm tra kích thước bộ đệm có thể bị vô hiệu hóa

Giới thiệu hàm sao chép chuỗi mới

  • Để ngăn rủi ro này, một hàm thay thế mang tên curlx_strcopy() đã được giới thiệu
    • Nhận các đối số gồm bộ đệm đích, kích thước bộ đệm, bộ đệm nguồn, độ dài chuỗi nguồn
    • Chỉ thực hiện bằng memcpy() khi cả việc sao chép và kết thúc null đều khả thi
    • Nếu thất bại, bộ đệm đích sẽ được khởi tạo thành chuỗi rỗng
  • Hàm này cần nhiều đối số và nhiều mã hơn so với strcpy(), nhưng đổi lại gắn chặt việc kiểm tra bộ đệm với thao tác sao chép để đảm bảo an toàn
  • cURL đã cấm hoàn toàn việc dùng strcpy() trong codebase và loại bỏ nó giống như với strncpy()

Chi tiết triển khai

  • Ví dụ định nghĩa hàm như sau
    void curlx_strcopy(char *dest, size_t dsize, const char *src, size_t slen)
    {
      DEBUGASSERT(slen < dsize);
      if(slen < dsize) {
        memcpy(dest, src, slen);
        dest[slen] = 0;
      }
      else if(dsize)
        dest[0] = 0;
    }
    
  • Thông qua DEBUGASSERT, cURL phát hiện sớm lỗi trong quá trình phát triển, còn ở môi trường phát hành thực tế thì hàm được thiết kế để luôn thành công
  • Giống strcpy, hàm này không có giá trị trả về và chọn cách bắt lỗi ở giai đoạn kiểm thử và fuzzing

Phản ứng từ cộng đồng

  • Một số nhà phát triển cho rằng nó tương tự strcpy_s() (C11 Annex K), nhưng cURL hiện vẫn dùng chuẩn C89
  • Những ý kiến khác đề xuất cần thêm giá trị trả về hoặc cải thiện cách xử lý khi bộ đệm không đủ
  • Đáp lại, phía cURL giải thích rằng “vì đây là hàm được thiết kế để luôn thành công nên không cần giá trị trả về”

Tác dụng phụ liên quan đến AI

  • Thay đổi lần này còn có thể ngăn các chatbot AI nhận diện sai việc dùng strcpy trong mã cURL rồi kết luận là ‘dễ tổn thương’
  • Tuy vậy, tác giả cũng lưu ý rằng “AI vẫn có thể tạo ra các báo cáo sai khác”, qua đó nhắc đến giới hạn của phân tích mã bằng AI

5 bình luận

 
ahwjdekf 2026-01-02

Nên dùng snprintf thay cho strcpy. Nếu trong code có strcpy thì phải tìm ra địa chỉ của lập trình viên đã tạo ra nó.

 
winmain 2026-01-02

Đây là cách tôi từng làm bằng mã debug hồi còn làm ở một công ty game cách đây 25 năm, chứ đâu chỉ riêng mỗi strcpy. Khi phát hành thì lại mở hết ra để tăng tốc độ rồi đem đi phục vụ. Thực ra phía game là nơi nhạy cảm nhất với xung đột bộ nhớ, nên khi làm cũng phải cực kỳ tỉnh táo và cẩn trọng, đến mức còn tự làm cả memory debugger để dùng. Nhưng nhìn lại ngày nay thì hóa ra khi đó mình đang tạo ra garbage collection. Đúng là một ký ức mơ hồ.

 
secwind 2026-01-02

Lỗi C4996 'strcpy': Hàm hoặc biến này có thể không an toàn. Hãy cân nhắc dùng strcpy_s thay vào đó. Để tắt cảnh báo ngừng hỗ trợ, hãy dùng _CRT_SECURE_NO_WARNINGS. Xem trợ giúp trực tuyến để biết chi tiết.

 
GN⁺ 2026-01-01
Ý kiến trên Hacker News
  • strcpy() không chỉ tệ về mặt bảo mật mà còn kém cả về hiệu năng
    Trước đây người ta nghĩ strcpy() hiệu quả khi không biết độ dài chuỗi, nhưng thực tế nó sao chép từng byte một nên CPU phải dự đoán nhánh, điều này không hiệu quả

    • Giờ tôi nghĩ nên loại bỏ bản thân chuỗi null-terminated bất cứ khi nào có thể
    • Gần đây tôi chưa thấy strcpy dùng scalar loop. Không rõ có phải chỉ như vậy trên kiến trúc ARM hay không
  • Các routine xử lý chuỗi của C đều có những ràng buộc lớn nên tôi thấy chúng khá vô dụng
    Vì vậy tôi nghĩ rất cần một thư viện ghi lại kích thước bộ nhớ đã cấp phát cùng với con trỏ chuỗi
    Có thể tham khảo thư viện bstring làm ví dụ

    • strncpy ra đời là để sao chép tên tệp có độ dài cố định. Xem giải thích chi tiết trong câu trả lời StackOverflow này
    • Việc không đưa thông tin độ dài vào chuỗi là vì trước đây cần tiết kiệm bộ nhớ. Khi đó chỉ một byte thôi cũng rất quý
    • Các hàm chuỗi của C gây ra vấn đề vì những người thiết kế đã thêm chúng vào khi chưa dự đoán đầy đủ hậu quả. Việc mảng bị ép chuyển thành con trỏ trong đối số hàm cũng là một sai lầm thiết kế nền tảng
    • Kiểu book-keeping bổ sung này trước kia là gánh nặng, nhưng giờ đã ở mức hoàn toàn có thể chấp nhận được
    • strncpy ban đầu là hàm để xử lý trường chuỗi có độ rộng cố định. Ví dụ nó dùng để đệm NUL vào trường như char username[20]. Xem thêm trong manual string_copying.7
  • Tôi thấy lạ là curlx_strcopy không trả về trạng thái thành công hay thất bại
    Có thể kiểm tra dest[0], nhưng cách này rất dễ gây lỗi và không trực quan

    • Bản trước trả về lỗi, còn bây giờ thì thất bại một cách im lặng và đặt chuỗi rỗng. Điều này thật kỳ quặc
    • Có lẽ người ta coi là thành công nếu DEBUGASSERT(slen < dsize); vượt qua, nhưng assert có thể bị loại bỏ trong bản build release. Tôi nghĩ mã lỗi tường minh sẽ tốt hơn
    • Với thiết kế như vậy, tôi cho rằng về sau rất dễ xuất hiện CVE
  • strncpy() ban đầu không dành cho chuỗi null-terminated mà cho các trường có độ dài cố định
    Vấn đề bắt đầu khi công cụ phân tích tĩnh khuyến nghị dùng strncpy thay cho strcpy, trong khi lựa chọn thay thế thực sự là snprintf hoặc strlcpy

    • strlcpy là hàm thuộc họ BSD nên không có trong POSIX. Khuyến nghị chính thức là stpecpy, nhưng gần như không có triển khai thực tế. Xem tài liệu liên quan
    • Lý do strncpy đệm phần sau ký tự null là để so sánh hiệu quả trong các trường tên có độ dài cố định như directory entry. Tài liệu cơ sở của chuẩn ANSI C cũng ghi rõ như vậy
  • API này tạo cảm giác giống Annex-K. Kích thước buffer đích có tính cả chỗ cho NUL, còn kích thước nguồn thì không
    Tôi nghĩ thà tự dùng memcpy còn hơn

  • Trong bài có câu “strcpymiếng mồi để AI tạo ra các báo cáo lỗ hổng sai” nghe rất ấn tượng

    • Thực tế không phải AI chỉ đơn giản chỉ ra strcpy là vấn đề, mà nó còn tạo ra các lập luận phức tạp nhưng có lỗi logic, khiến người bảo trì phải vất vả kiểm chứng
    • Những người gửi các báo cáo sai như vậy либо không biết AI có thể sai, либо đơn giản là không quan tâm. Dù sao thì báo cáo sai cũng chẳng mất gì
    • Cuối cùng, vấn đề là những người dùng AI cho mục đích không phù hợp
  • Nguyên tắc “kiểm tra gần chỗ dùng” là tốt, nhưng sẽ mơ hồ khi cần kiểm tra từ sớm trong vòng đời của dữ liệu
    Tôi nghĩ sẽ hay hơn nếu có thể phân biệt bằng kiểu dữ liệu rằng đây là “dữ liệu đã được kiểm chứng”, giống như kiểu Result của Rust

    • Result chỉ đơn thuần chứa thành công/thất bại, chứ không đảm bảo trạng thái đã được kiểm chứng. Thay vào đó nên có một kiểu riêng chỉ có thể tạo ra sau khi đi qua bước kiểm chứng. Đó là triết lý “parse, don’t validate
    • Lý tưởng nhất là thực hiện kiểm chứng không phải gần mã tiêu thụ mà là sớm nhất có thể ở biên của hệ thống. Tuy nhiên điều đó đòi hỏi hệ thống kiểu đủ biểu đạt
    • Trong trường hợp này, cũng có thể phân biệt kiểu như String và CharSequence của Java
  • Sự khác biệt off-by-one giữa kích thước buffer và độ dài chuỗi là một vấn đề cực kỳ tệ về mặt tính dễ dùng. Sau này rất dễ tiếp tục gây lỗi

  • Hàm sao chép chuỗi mới được đề xuất sẽ xóa trống buffer đích và trả về void nếu không thể sao chép
    Nhưng tôi nghĩ trong trường hợp như vậy nên xử lý như lỗi và không đụng vào buffer. Chỉ chặn bằng DEBUGASSERT thì không yên tâm

  • Chúc mừng dự án đã hoàn thành. C/C++ nếu nỗ lực thì vẫn có thể đạt được an toàn bộ nhớ
    Tuy vậy trong môi trường di động, cỡ chữ của đồ thị quá nhỏ nên rất khó đọc

    • Việc loại bỏ strcpy không có nghĩa là mã sẽ trở nên an toàn bộ nhớ
    • Có vẻ font của đồ thị được thiết kế cho in ấn. Với blog thì nó quá nhỏ
 
hiongun 2026-01-04

Chuyển hẳn sang ngôn ngữ C3 cũng là một lựa chọn hay. Đây là một dự án giữ cú pháp của C với mức thay đổi tối thiểu, đồng thời bổ sung các tính năng hiện đại nên cũng dễ chuyển đổi.