Tại sao strlcpy và strlcat được coi là không an toàn?


80

Tôi hiểu điều đó strlcpystrlcatđược thiết kế để thay thế an toàn cho strncpystrncat. Tuy nhiên, một số người vẫn cho rằng họ không an toàn và chỉ đơn giản là gây ra một loại vấn đề khác .

Ai đó có thể đưa ra một ví dụ về cách sử dụng strlcpyhoặc strlcat(tức là một hàm luôn null kết thúc chuỗi của nó) có thể dẫn đến các vấn đề bảo mật không?

Ulrich Drepper và James Antill tuyên bố điều này là đúng, nhưng không bao giờ cung cấp ví dụ hoặc làm rõ điểm này.

Câu trả lời:


112

Thứ nhất, strlcpychưa bao giờ được coi là phiên bản an toàn của strncpy(và strncpychưa bao giờ được coi là phiên bản an toàn của strcpy). Hai chức năng này hoàn toàn không liên quan đến nhau. strncpylà một hàm hoàn toàn không liên quan đến chuỗi C (tức là chuỗi kết thúc bằng null). Thực tế là nó có str...tiền tố trong tên của nó chỉ là một sai lầm lịch sử. Lịch sử và mục đích của strncpynó là nổi tiếng và được tài liệu đầy đủ. Đây là một hàm được tạo ra để làm việc với các chuỗi "độ rộng cố định" (không phải với C-string) được sử dụng trong một số phiên bản lịch sử của hệ thống tệp Unix. Một số lập trình viên ngày nay bị nhầm lẫn bởi tên của nó và cho rằng strncpybằng cách nào đó được cho là dùng như một hàm sao chép chuỗi C có độ dài giới hạn (một anh chị em "an toàn" củastrcpy), trong thực tế hoàn toàn vô nghĩa và dẫn đến việc thực hành lập trình tồi. Thư viện chuẩn C ở dạng hiện tại không có chức năng sao chép chuỗi C có độ dài giới hạn. Đây là nơi strlcpyphù hợp strlcpythực sự là một chức năng sao chép độ dài giới hạn thực sự được tạo ra để làm việc với C-string. strlcpythực hiện đúng mọi thứ mà một chức năng sao chép có độ dài giới hạn phải làm. Lời chỉ trích duy nhất mà người ta có thể nhắm vào nó là nó, rất tiếc, nó không chuẩn.

Thứ hai, strncatmặt khác, thực sự là một hàm hoạt động với C-string và thực hiện một phép nối có độ dài giới hạn (nó thực sự là một người anh em "an toàn" của strcat). Để sử dụng chức năng này đúng cách, lập trình viên phải đặc biệt lưu ý, vì tham số kích thước mà hàm này chấp nhận không thực sự là kích thước của bộ đệm nhận kết quả, mà là kích thước của phần còn lại của nó (cũng là ký tự dấu chấm được tính ngầm). Điều này có thể gây nhầm lẫn, vì để gắn kích thước đó với kích thước của bộ đệm, lập trình viên phải nhớ thực hiện một số phép tính bổ sung, thường được sử dụng để chỉ trích strncat.strlcatxử lý các vấn đề này, thay đổi giao diện để không cần tính toán thêm (ít nhất là trong mã gọi). Một lần nữa, cơ sở duy nhất tôi thấy người ta có thể chỉ trích điều này là chức năng không chuẩn. Ngoài ra, các hàm từ strcatnhóm là thứ bạn sẽ không thấy trong mã chuyên nghiệp thường xuyên do khả năng sử dụng hạn chế của ý tưởng nối chuỗi dựa trên quét lại.

Về cách các chức năng này có thể dẫn đến các vấn đề bảo mật ... Đơn giản là chúng không thể. Chúng không thể dẫn đến các vấn đề bảo mật ở bất kỳ mức độ nào hơn chính ngôn ngữ C có thể "dẫn đến các vấn đề bảo mật". Bạn thấy đấy, trong một thời gian đã có rất nhiều ý kiến ​​cho rằng ngôn ngữ C ++ phải đi theo hướng phát triển thành một hương vị kỳ lạ nào đó của Java. Tình cảm này đôi khi cũng tràn sang lĩnh vực ngôn ngữ C, dẫn đến những lời chỉ trích khá khó hiểu và gượng ép đối với các tính năng của ngôn ngữ C và các tính năng của thư viện chuẩn C. Tôi nghi ngờ rằng chúng tôi cũng có thể đang đối phó với một điều gì đó tương tự trong trường hợp này, mặc dù tôi chắc chắn hy vọng mọi thứ không thực sự tệ như vậy.


9
Tôi không hoàn toàn đồng ý. Sẽ rất tuyệt nếu strlcpystrlcatsẽ báo cáo một số loại tình trạng lỗi nếu chúng chạm vào giới hạn kích thước bộ đệm đích. Mặc dù bạn có thể kiểm tra độ dài trả về để kiểm tra điều này, nhưng nó không rõ ràng. Nhưng tôi nghĩ đó là một chỉ trích nhỏ. Lập luận 'họ khuyến khích sử dụng chuỗi C, và vì vậy chúng xấu' là ngớ ngẩn.
Omnifarious

7
"Làm thế nào những hàm này có thể dẫn đến các vấn đề bảo mật" - fwiw Tôi nghĩ vấn đề ở đây là một số hàm C khó sử dụng chính xác hơn những hàm khác. Một số người lầm tưởng rằng có một ngưỡng độ khó đặc biệt, dưới đó một chức năng là "an toàn" và trên đó là "không an toàn". Những người như vậy cũng thường của niềm tin rằng strcpylà trên ngưỡng và do đó "không an toàn", và chức năng chuỗi sao chép ưa thích của họ (cho dù đó là strlcpy, strcpy_shoặc thậm chí strncpy) là dưới ngưỡng và do đó "an toàn".
Steve Jessop

31
Có rất nhiều lý do để không thích strlcpy / strlcat, nhưng bạn không nêu rõ bất kỳ lý do nào. Thảo luận về C ++ và Java là không liên quan. Câu trả lời này chỉ không hữu ích cho chủ đề mà câu hỏi thực sự hỏi.
John Ripley

24
@John Ripley: Thứ nhất, tôi không "nói rõ bất kỳ điều gì trong số họ" đơn giản vì tôi không biết bất kỳ lý do nào để không thích strlcpy/strlcat. Người ta có thể "không thích" khái niệm chung về chuỗi có kết thúc bằng 0, nhưng đó không phải là điều mà câu hỏi đặt ra. Nếu bạn biết "rất nhiều lý do để không thích strlcpy/strlcat", có lẽ bạn nên viết câu trả lời của riêng mình thay vì mong đợi tôi có thể đọc được suy nghĩ của người khác.
AnT

8
@John Ripley: Thứ hai, câu hỏi đề cập cụ thể đến một số "vấn đề bảo mật" bị cáo buộc strlcpy/strlcat. Mặc dù tôi tin rằng tôi hiểu điều này là gì, nhưng cá nhân tôi từ chối công nhận đó là "vấn đề bảo mật" trong lĩnh vực ngôn ngữ C truyền thống, như tôi biết. Điều đó tôi đã nêu trong câu trả lời của mình.
AnT

31

Lời chỉ trích của Ulrich dựa trên ý tưởng rằng một chuỗi cắt ngắn không được chương trình phát hiện có thể dẫn đến các vấn đề bảo mật, thông qua logic không chính xác. Do đó, để bảo mật, bạn cần kiểm tra việc cắt bớt. Để làm điều này cho một chuỗi nối có nghĩa là bạn đang thực hiện kiểm tra dọc theo các dòng của điều này:

Bây giờ, strlcatthực hiện hiệu quả việc kiểm tra này, nếu lập trình viên nhớ kiểm tra kết quả - vì vậy bạn có thể sử dụng nó một cách an toàn:

Ý của Ulrich là vì bạn phải có destlensourcelenxung quanh (hoặc tính toán lại chúng, đó là những gì strlcathiệu quả), bạn cũng có thể chỉ sử dụng hiệu quả hơn memcpy:

(Trong đoạn mã trên, dest_maxlenlà độ dài tối đa của chuỗi có thể được lưu trữ dest- nhỏ hơn một kích thước của destbộ đệm. dest_bufferlenLà kích thước đầy đủ của dest buffer).


13
Khả năng đọc mã của Drepper rất tệ. Với strlcpy (hoặc bất kỳ hàm str nào), tôi biết trực tiếp rằng tôi đang sao chép một chuỗi C kết thúc bằng 0. Với memcpynó có thể là bất kỳ loại bộ nhớ nào và tôi có một thứ nguyên bổ sung để kiểm tra khi cố gắng hiểu mã. Tôi có một ứng dụng cũ để gỡ lỗi trong đó mọi thứ được thực hiện với memcpy, đó là một PITA thực sự để sửa. Sau khi chuyển sang hàm String chuyên dụng, nó sẽ dễ đọc hơn nhiều (và nhanh hơn vì strlencó thể loại bỏ nhiều thứ không cần thiết ).
Patrick Schlüter

3
@domen: Vì kích thước để sao chép đã được biết trước, vậy memcpy()là đủ (và có khả năng hiệu quả hơn strcpy()).
caf

1
Chà, thật khó hiểu khi có nó trong các hoạt động chuỗi. Và theo tôi biết hiệu quả phụ thuộc vào việc thực hiện và không được tiêu chuẩn hóa.
domen

8
@domen: memcpy() một hoạt động chuỗi - sau cùng thì nó được khai báo <string.h>.
caf

2
@domen Tôi đồng ý rằng có khả năng gây nhầm lẫn, nhưng thực tế là dù sao thì làm việc với chuỗi C cũng tương đối nhiều với bộ nhớ thô. Có thể cho rằng, tất cả chúng ta sẽ tốt hơn nếu mọi người ngừng nghĩ C là có "chuỗi" (khác biệt với bất kỳ phần bộ nhớ liền kề nào khác).
mtraceur

19

Khi mọi người nói, " strcpy()nguy hiểm, hãy sử dụng strncpy()thay thế" (hoặc các câu tương tự về strcat()v.v., nhưng tôi sẽ sử dụng strcpy()ở đây làm trọng tâm của mình), họ có nghĩa là không có giới hạn kiểm tra strcpy(). Do đó, một chuỗi quá dài sẽ dẫn đến vượt quá bộ đệm. Họ đúng. Sử dụng strncpy()trong trường hợp này sẽ ngăn chặn quá tải bộ đệm.

Tôi cảm thấy rằng điều đó strncpy()thực sự không sửa được lỗi: nó giải quyết một vấn đề mà một lập trình viên giỏi có thể dễ dàng tránh được.

Là một lập trình viên C, bạn phải biết kích thước đích trước khi cố gắng sao chép chuỗi. Đó cũng là giả định trong strncpy()strlcpy()của tham số cuối cùng: bạn cung cấp kích thước đó cho chúng. Bạn cũng có thể biết kích thước nguồn trước khi sao chép chuỗi. Sau đó, nếu điểm đến không đủ lớn, đừng gọistrcpy() . Phân bổ lại bộ đệm hoặc làm điều gì đó khác.

Tại sao tôi không thích strncpy()?

  • strncpy() là một giải pháp tồi trong hầu hết các trường hợp: chuỗi của bạn sẽ bị cắt ngắn mà không có bất kỳ thông báo nào — tôi thà viết thêm mã để tự tìm hiểu điều này và sau đó thực hiện hành động mà tôi muốn thực hiện, thay vì để một số hàm quyết định tôi về những gì phải làm.
  • strncpy()là rất kém hiệu quả. Nó ghi vào từng byte trong bộ đệm đích. Bạn không cần hàng ngàn cái đó '\0'ở cuối điểm đến của mình.
  • Nó không viết kết thúc '\0'nếu đích không đủ lớn. Vì vậy, dù thế nào bạn cũng phải tự mình làm như vậy. Sự phức tạp của việc làm này không đáng để bạn gặp rắc rối.

Bây giờ, chúng tôi đến strlcpy(). Những thay đổi từ strncpy()làm cho nó tốt hơn, nhưng tôi không chắc liệu hành vi cụ thể có strl*đảm bảo sự tồn tại của chúng hay không: chúng quá cụ thể. Bạn vẫn phải biết kích thước đích. Nó hiệu quả hơn strncpy()vì nó không nhất thiết phải ghi vào từng byte trong đích. Nhưng nó giải quyết một vấn đề có thể được giải quyết bằng cách thực hiện: *((char *)mempcpy(dst, src, n)) = 0;.

Tôi không nghĩ rằng bất kỳ ai nói điều đó strlcpy()hoặc strlcat()có thể dẫn đến các vấn đề bảo mật, những gì họ (và tôi) đang nói rằng họ có thể dẫn đến lỗi, ví dụ, khi bạn mong đợi chuỗi hoàn chỉnh được viết thay vì một phần của nó.

Vấn đề chính ở đây là: cần sao chép bao nhiêu byte? Lập trình viên phải biết điều này và nếu anh ta không, strncpy()hoặc strlcpy()sẽ không cứu anh ta.

strlcpy()strlcat()không phải là tiêu chuẩn, không phải ISO C hay POSIX. Vì vậy, việc sử dụng chúng trong các chương trình di động là không thể. Trên thực tế, strlcat()có hai biến thể khác nhau: việc triển khai Solaris khác với những biến thể khác đối với các trường hợp cạnh liên quan đến độ dài 0. Điều này khiến nó thậm chí còn ít hữu ích hơn so với cách khác.


3
strlcpynhanh hơn memcpytrên nhiều kiến ​​trúc, đặc biệt nếu memcpybản sao dữ liệu theo sau không cần thiết. strlcpycũng trả về lượng dữ liệu bạn đã bỏ lỡ, điều này có thể cho phép bạn khôi phục nhanh hơn và ít mã hơn.

1
@jbcreix: quan điểm của tôi là không có dữ liệu nào bị bỏ lỡ và ntrong cuộc gọi memcpy của tôi là số byte chính xác được ghi, vì vậy hiệu quả cũng không phải là vấn đề nhiều.
Alok Singhal

5
Và làm thế nào để bạn có được n đó? Thứ n duy nhất bạn có thể biết trước là kích thước bộ đệm. Tất nhiên nếu bạn đề xuất triển khai lại strlcpymỗi khi bạn cần sử dụng nó memcpystrlenđiều đó cũng ổn, nhưng tại sao dừng lại ở strlcpy, bạn cũng không cần một memcpyhàm, bạn có thể sao chép từng byte một. Việc triển khai tham chiếu chỉ lặp lại dữ liệu một lần trong trường hợp bình thường và điều đó tốt hơn cho hầu hết các kiến ​​trúc. Nhưng ngay cả khi triển khai tốt nhất được sử dụng strlen+ memcpy, đó vẫn không có lý do gì để không phải triển khai lại một strcpy an toàn nhiều lần.

1
Alok, với việc strlcpybạn quét qua chuỗi đầu vào chỉ một lần trong trường hợp tốt (nên chiếm đa số) và hai lần trong trường hợp xấu, với strlen+ của memcpybạn, bạn luôn xem qua nó hai lần. Nếu nó tạo ra sự khác biệt trong thực tế là một câu chuyện khác.
Patrick Schlüter

4
"* ((char *) mempcpy (dst, src, n)) = 0;" - Đúng - Vâng, Rõ ràng chính xác, số Fails xem xét mã ......
mattnz

12

Tôi nghĩ Ulrich và những người khác nghĩ rằng nó sẽ mang lại cảm giác an toàn sai lầm. Việc vô tình cắt bớt chuỗi có thể có ý nghĩa bảo mật đối với các phần khác của mã (ví dụ: nếu đường dẫn hệ thống tệp bị cắt ngắn, chương trình có thể không thực hiện các hoạt động trên tệp dự định).


8
Ví dụ: một ứng dụng email có thể cắt bớt tên tệp của tệp đính kèm email từ malware.exe.jpgthành malware.exe.
Chris Peterson

1
@ChrisPeterson Đó là lý do tại sao một nhà phát triển giỏi luôn kiểm tra các giá trị trả về, trong trường hợp là các hàm strl *, biết liệu dữ liệu có bị cắt bớt hay không và hành động cho phù hợp.
Tom Lint

"Ulrich và những người khác nghĩ rằng nó sẽ mang lại cảm giác an toàn sai lầm ...." - Lol ... trong khi đó, Ulrich và bạn bè xuất hiện thường xuyên trên BugTraq và Full Disclosure cho lần xuất hiện của họ. Họ nên sử dụng các chức năng an toàn hơn và tránh hầu hết các vấn đề của họ. Sau đó, họ có thể bắt đầu nói với người khác cách viết mã an toàn hơn ...
jww Ngày

7

Có hai "vấn đề" liên quan đến việc sử dụng các hàm strl:

  1. Bạn phải kiểm tra các giá trị trả về để tránh bị cắt bớt.

Những người viết bản nháp tiêu chuẩn c1x và Drepper, cho rằng các lập trình viên sẽ không kiểm tra giá trị trả về. Drepper nói rằng bằng cách nào đó chúng ta nên biết độ dài và sử dụng memcpy và tránh hoàn toàn các hàm chuỗi _TRUNCATE. Ý tưởng là mọi người có nhiều khả năng sử dụng if (strncpy_s (...)).

  1. Không thể được sử dụng trên không phải chuỗi.

Một số người nghĩ rằng các hàm chuỗi không bao giờ bị lỗi ngay cả khi được cung cấp dữ liệu không có thật. Điều này ảnh hưởng đến các chức năng tiêu chuẩn như strlen, trong điều kiện bình thường sẽ mặc định. Tiêu chuẩn mới sẽ bao gồm nhiều chức năng như vậy. Việc kiểm tra tất nhiên có một hình phạt thực hiện.

Ưu điểm của các hàm tiêu chuẩn được đề xuất là bạn có thể biết bạn đã bỏ lỡ bao nhiêu dữ liệu với các hàm strl .


lưu ý rằng đó strncpy_skhông phải là một phiên bản an toàn strncpynhưng về cơ bản là một phiên bản strlcpythay thế.

6

Tôi không nghĩ strlcpystrlcatđang coi là không an toàn hoặc ít nhất nó không phải là lý do tại sao chúng không được bao gồm trong glibc - sau tất cả, glibc bao gồm strncpy và thậm chí strcpy.

Họ bị chỉ trích là họ bị cho là không hiệu quả chứ không phải không an toàn .

Theo bài báo về Khả năng di chuyển an toàn của Damien Miller:

API strlcpy và strlcat kiểm tra chính xác giới hạn của bộ đệm mục tiêu, nul-end trong mọi trường hợp và trả về độ dài của chuỗi nguồn, cho phép phát hiện việc cắt bớt. API này đã được hầu hết các hệ điều hành hiện đại và nhiều gói phần mềm độc lập thông qua, bao gồm OpenBSD (nơi nó bắt nguồn), Sun Solaris, FreeBSD, NetBSD, nhân Linux, rsync và dự án GNOME. Ngoại lệ đáng chú ý là thư viện C tiêu chuẩn GNU, glibc [12], mà người bảo trì kiên định từ chối đưa vào các API cải tiến này, gắn nhãn chúng là "đồ tào lao BSD kém hiệu quả kinh khủng"[4], mặc dù có bằng chứng trước đó cho thấy chúng nhanh hơn hầu hết các trường hợp so với các API mà chúng thay thế [13]. Do đó, hơn 100 gói phần mềm có trong cây cổng OpenBSD duy trì các thay thế strlcpy và / hoặc strlcat của riêng chúng hoặc các API tương đương - không phải là trạng thái lý tưởng.

Đó là lý do tại sao chúng không có sẵn trong glibc, nhưng không hẳn là chúng không có trên Linux. Chúng có sẵn trên Linux trong libbsd:

Chúng được đóng gói trong Debian và Ubuntu và các bản phân phối khác. Bạn cũng có thể chỉ cần lấy một bản sao và sử dụng trong dự án của mình - nó ngắn và theo giấy phép dễ dàng:


3

Bảo mật không phải là boolean. Các hàm C không hoàn toàn là "an toàn" hoặc "không an toàn", "an toàn" hoặc "không an toàn". Khi được sử dụng sai, một thao tác gán đơn giản trong C có thể "không an toàn". strlcpy () và strlcat () có thể được sử dụng một cách an toàn (securely) cũng như strcpy () và strcat () có thể được sử dụng an toàn khi lập trình viên cung cấp những đảm bảo cần thiết về việc sử dụng đúng.

Điểm chính với tất cả các hàm chuỗi C này, tiêu chuẩn và không tiêu chuẩn, là mức độ mà chúng giúp sử dụng an toàn / bảo mật dễ dàng . strcpy () và strcat () không phải là tầm thường để sử dụng một cách an toàn; điều này được chứng minh bằng số lần các lập trình viên C đã làm sai trong nhiều năm và các lỗ hổng và khai thác xấu đã xảy ra sau đó. strlcpy () và strlcat () và vì vấn đề đó, strncpy () và strncat (), strncpy_s () và strncat_s (), dễ sử dụng hơn một chút một cách an toàn, nhưng vẫn không tầm thường. Chúng có không an toàn / không an toàn không? Không nhiều hơn memcpy (), khi được sử dụng sai.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.