Tại sao strncpy không null chấm dứt?


76

strncpy()được cho là bảo vệ khỏi sự cố tràn bộ đệm. Nhưng nếu nó ngăn tràn mà không kết thúc null, thì rất có thể một hoạt động chuỗi tiếp theo sẽ bị tràn. Vì vậy, để bảo vệ khỏi điều này, tôi thấy mình phải:


man strncpy cho:

Các strncpy()chức năng cũng tương tự, ngoại trừ việc không quá nbyte srcđược sao chép. Do đó, nếu không có byte nào trong số các nbyte đầu tiên của src, kết quả sẽ không được kết thúc bằng null.

Nếu không có null chấm dứt một cái gì đó dường như vô tội như:

... có thể bị rơi.


Có lựa chọn thay thế tốt hơn, an toàn hơn strncpy()không?


1
Lưu ý rằng trên MacOS X (BSD), trang man cho biết (of ' extern char *strncpy(char * restrict s1, const char * restrict s2, size_t n);'): Hàm strncpy () sao chép nhiều nhất n ký tự từ s2 vào s1. Nếu s2 dài dưới n ký tự, phần còn lại của s1 được lấp đầy bằng các ký tự `\ 0 '. Nếu không, s1 không bị kết thúc.
Jonathan Leffler 21/09/09

Không nên là đích [LEN-1] = '\ 0'; ?
codeObserver

1
Đây là cách tôi nghĩ chúng ta sẽ tạo một bản sao của chuỗi: int LEN = src.len; str * dest = new char [LEN + 1]; strncpy (dest, src, LEN); đích [LEN] = '\ 0';
codeObserver

Luôn sử dụng memset trên chuỗi đích là cách tiếp cận an toàn nhất nếu bạn chắc chắn kích thước của chuỗi sẽ không vượt quá độ dài bộ đệm đích.
koolvcvc

viết chức năng riêng của mình, tôi không nghĩ rằng nên là một nhiệm vụ khó khăn
Megharaj

Câu trả lời:


46

strncpy()không nhằm mục đích sử dụng an toàn hơn strcpy(), nó được sử dụng để chèn một chuỗi vào giữa chuỗi khác.

Tất cả các chức năng xử lý chuỗi "an toàn" đó như snprintf()vsnprintf()là các bản sửa lỗi đã được thêm vào trong các tiêu chuẩn sau này để giảm thiểu khai thác tràn bộ đệm, v.v.

Wikipedia đề cập đến strncat()việc thay thế cho việc viết két sắt của riêng bạn strncpy():

BIÊN TẬP

Tôi đã bỏ lỡ strncat()vượt quá ký tự LEN khi null kết thúc chuỗi nếu nó dài hơn hoặc bằng ký tự LEN.

Dù sao, điểm của việc sử dụng strncat()thay vì bất kỳ giải pháp cây nhà lá vườn nào chẳng hạn như memcpy(..., strlen(...))/ bất cứ điều gì là việc triển khai strncat()có thể được tối ưu hóa mục tiêu / nền tảng trong thư viện.

Tất nhiên, bạn cần phải kiểm tra xem dst có giữ ít nhất nullchar không, vì vậy cách sử dụng đúng strncat()sẽ là:

Tôi cũng thừa nhận rằng điều đó strncpy()không hữu ích lắm cho việc sao chép một chuỗi con thành một chuỗi khác, nếu src ngắn hơn n char, chuỗi đích sẽ bị cắt bớt.


28
"nó được cho là được sử dụng để chèn một chuỗi vào giữa chuỗi khác" - không, nó nhằm mục đích ghi một chuỗi vào trường có độ rộng cố định, chẳng hạn như trong một mục nhập thư mục. Đó là lý do tại sao nó đệm bộ đệm đầu ra bằng NUL nếu (và chỉ khi) chuỗi nguồn quá ngắn.
Steve Jessop 21/09/09

3
Cài đặt * dst = '\ 0' giúp điều này an toàn hơn như thế nào? Nó vẫn có vấn đề ban đầu là cho phép bạn ghi vượt quá phần cuối của bộ đệm đích.
Adam Liss 21/09/09

3
nghe có vẻ tốt nhưng nó không phải là strncat (dst, src, LEN-1) vì nó sẽ viết thêm một ký tự?
Timothy Pratley 21/09/09

3
@Jonathan: Thực ra an toàn sẽ là một kiểu dữ liệu kết hợp một con trỏ tới một bộ đệm char, với độ dài của bộ đệm đó. Nhưng tất cả chúng ta đều biết điều đó sẽ không xảy ra. Cá nhân tôi cảm thấy mệt mỏi với tất cả những nỗ lực này để tạo ra thứ gì đó vốn dĩ không an toàn (các lập trình viên cố gắng tôn trọng chính xác độ dài của bộ đệm), an toàn hơn một chút. Nó không phải là nếu chúng tôi hiện có 50% quá nhiều vượt đệm, vì vậy nếu duy nhất chúng ta có thể làm cho chuỗi xử lý 50% an toàn hơn, chúng tôi sẽ ổn thôi :-(
Steve Jessop

1
+1 để không lặp lại rác rưởi rằng strncpy bằng cách nào đó là một phiên bản an toàn của strcpy - phiên bản cũ có một loạt vấn đề riêng.
paxdiablo

26

Ban đầu, hệ thống tệp UNIX Phiên bản thứ 7 (xem DIR (5)) có các mục nhập thư mục giới hạn tên tệp ở 14 byte; mỗi mục nhập trong một thư mục bao gồm 2 byte cho số inode cộng với 14 byte cho tên, đệm null thành 14 ký tự, nhưng không nhất thiết phải kết thúc bằng rỗng. Tôi tin rằng nó strncpy()được thiết kế để hoạt động với các cấu trúc thư mục đó - hoặc, ít nhất, nó hoạt động hoàn hảo cho cấu trúc đó.

Xem xét:

  • Tên tệp 14 ký tự không được kết thúc bằng rỗng.
  • Nếu tên ngắn hơn 14 byte, nó sẽ được đệm bằng rỗng đến độ dài đầy đủ (14 byte).

Đây chính xác là những gì sẽ đạt được bằng cách:

Vì vậy, nó strncpy()được trang bị lý tưởng cho ứng dụng thích hợp ban đầu của nó. Nó chỉ ngẫu nhiên về việc ngăn chặn tràn các chuỗi bị kết thúc bằng null.

(Lưu ý rằng phần đệm null lên đến độ dài 14 không phải là chi phí nghiêm trọng - nếu độ dài của bộ đệm là 4 KB và tất cả những gì bạn muốn là sao chép an toàn 20 ký tự vào đó, thì 4075 null bổ sung là quá mức cần thiết nghiêm trọng và có thể dễ dàng dẫn đến hành vi bậc hai nếu bạn liên tục thêm vật liệu vào bộ đệm dài.)


2
Tình huống cụ thể đó có thể không rõ ràng, nhưng không phải là hiếm khi có cấu trúc dữ liệu với các trường chuỗi có độ dài cố định được đệm null nhưng không kết thúc bằng null. Thật vậy, nếu một người đang lưu trữ dữ liệu định dạng cố định, thì đó thường là cách hiệu quả nhất để làm điều đó.
supercat

24

Đã có các triển khai mã nguồn mở như strlcpy thực hiện sao chép an toàn.

http://en.wikipedia.org/wiki/Strlcpy

Trong các tài liệu tham khảo có các liên kết đến các nguồn.


1
Chưa kể, di động, nhanh chóng và đáng tin cậy. Bạn vẫn có thể lạm dụng nó, nhưng rủi ro là các đơn đặt hàng có độ lớn thấp hơn. IMO, strncpy nên không được dùng nữa và được thay thế bằng cùng một hàm được gọi là dirnamecpy hoặc một cái gì đó tương tự. strncpy không phải là bản sao chuỗi an toàn và chưa bao giờ bị như vậy.

9

Strncpy an toàn hơn trước các cuộc tấn công tràn ngăn xếp bởi người dùng chương trình của bạn, nó không bảo vệ bạn khỏi các lỗi lập trình viên thực hiện, chẳng hạn như in một chuỗi không kết thúc bằng null, theo cách bạn đã mô tả.

Bạn có thể tránh gặp sự cố do sự cố mà bạn đã mô tả bằng cách giới hạn số ký tự được printf in ra:


Việc sử dụng các lĩnh vực chính xác để hạn chế số ký tự in bằng %sđã có được một trong những tính năng tối nghĩa nhất của C.
David Thornley

@DavidThornley Nó được ghi lại rất rõ ràng trong K&R dưới sprintf.
weston

@weston: Và ở Harbison & Steele, đó là những gì tôi có ở đây trong công việc. Bây giờ, trong những cuốn sách C phổ biến nào, ngoài hai cuốn sách đó, điều này được đề cập đến? Mọi tính năng nên được đề cập trong K&R và H&S (và được đề cập trong Tiêu chuẩn), vì vậy nếu đó là tiêu chuẩn về sự che khuất thì không có tính năng nào bị che khuất.
David Thornley

@DavidThornley Tôi chỉ muốn cân bằng nhận xét của bạn, bởi vì bằng cách đặt "một trong những tính năng khó hiểu nhất", nó làm cho câu trả lời này trông xấu và mọi người có thể ngừng sử dụng nó. Điều này là sai vì nó là một tính năng hoàn toàn hợp lệ, được ghi chép đầy đủ, cũng như được ghi lại như bất kỳ việc sử dụng trường độ chính xác nào khác. "Ít người biết đến" dường như là một vấn đề quan điểm, vì cá nhân tôi thấy điều này được sử dụng rất nhiều.
weston

8

Một số lựa chọn thay thế mới được chỉ định trong ISO / IEC TR 24731 (Kiểm tra https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/coding/317-BSI.html để biết thông tin). Hầu hết các hàm này nhận một tham số bổ sung chỉ định độ dài tối đa của biến đích, đảm bảo rằng tất cả các chuỗi đều được kết thúc bằng null và có tên kết thúc bằng _s(cho "safe"?) Để phân biệt chúng với các phiên bản "không an toàn" trước đó của chúng . 1

Rất tiếc, chúng vẫn đang được hỗ trợ và có thể không khả dụng với bộ công cụ cụ thể của bạn. Các phiên bản sau của Visual Studio sẽ đưa ra cảnh báo nếu bạn sử dụng các chức năng cũ không an toàn.

Nếu các công cụ của bạn không hỗ trợ các chức năng mới, thì việc tạo trình bao bọc của riêng bạn cho các chức năng cũ sẽ khá dễ dàng. Đây là một ví dụ:

Bạn có thể thay đổi chức năng để phù hợp với nhu cầu của mình, chẳng hạn như luôn sao chép nhiều chuỗi nhất có thể mà không bị tràn. Trên thực tế, việc triển khai VC ++ có thể thực hiện điều này nếu bạn chuyển _TRUNCATEnhư sau count.




1 Tất nhiên, bạn vẫn cần phải chính xác về kích thước của bộ đệm đích: nếu bạn cung cấp bộ đệm 3 ký tự nhưng nói rằng strcpy_s()nó có khoảng trống cho 25 ký tự, bạn vẫn đang gặp rắc rối.


Bạn không thể định nghĩa về mặt pháp lý một chức năng có tên bắt đầu bằng * str, rằng "namespace" được duy trì trong C.
Thư giãn

2
Nhưng ủy ban ISO C có thể - và đã làm. Xem thêm: stackoverflow.com/questions/372980/...
Jonathan Leffler

@Jonathan: Cảm ơn bạn đã tham khảo chéo câu hỏi của riêng bạn, câu hỏi này cung cấp rất nhiều thông tin hữu ích bổ sung.
Adam Liss 21/09/09

5

Sử dụng strlcpy(), được chỉ định ở đây:http://www.courtesan.com/todd/papers/strlcpy.html

Nếu libc của bạn không có triển khai, hãy thử cách này:

(Do tôi viết vào năm 2004 - dành riêng cho phạm vi công cộng.)


Xin hãy khai sáng cho tôi, tại sao bạn muốn kết quả luôn là độ dài của chuỗi src? theo ý kiến ​​của tôi, trả về srclensẽ tốt hơn vì chúng tôi sẽ biết có bao nhiêu ký tự thực sự được sao chép.
Lê Quang Duy

@ LêQuangDuy, nó phù hợp với thông số kỹ thuật ( freebsd.org/cgi/man.cgi?query=strlcpy&sektion=3#end ): như snprintf , strlcat , nó trả về kích thước của chuỗi mà nó đã cố gắng viết, vì vậy người gọi có thể cung cấp bộ đệm lớn hơn và gọi lại hàm để lưu trữ mọi thứ.
Jonathan Lidbeck

3

strncpy hoạt động trực tiếp với bộ đệm chuỗi có sẵn, nếu bạn đang làm việc trực tiếp với bộ nhớ của mình, bây giờ bạn PHẢI kích thước bộ đệm và bạn có thể đặt '\ 0' theo cách thủ công.

Tôi tin rằng không có giải pháp thay thế nào tốt hơn ở C đơn giản, nhưng nó không thực sự tệ đến mức nếu bạn cẩn thận như khi chơi với bộ nhớ thô.


3

Thay vì strncpy() , bạn có thể sử dụng

Đây là một chữ lót sao chép nhiều nhất size-1các ký tự không phải là ký tự rỗng từ srcđến destvà thêm một dấu chấm hết rỗng:


Chúng tôi đang sử dụng macro tương đương với snprintf(buffer, sizeof(buffer), "%s", src). Hoạt động tốt miễn là bạn nhớ không bao giờ sử dụng nó ở các điểm đến char *
che

3

Tôi luôn thích:

để khắc phục sự cố sau đó, nhưng đó thực sự chỉ là vấn đề ưu tiên.


1
Có nên khởi tạo tất cả các bộ đệm bằng 0 hay không là một chủ đề tranh luận theo đúng nghĩa của nó. Cá nhân tôi thích làm như vậy trong quá trình phát triển / gỡ lỗi, vì nó có xu hướng tạo ra lỗi rõ ràng hơn, nhưng có rất nhiều tùy chọn khác ("rẻ hơn").
Adam Liss 21/09/09

7
bạn chỉ cần đặt dest[LEN-1]thành 0- các byte khác sẽ được lấp đầy strncpy()nếu cần (hãy nhớ: strncpy(s,d,n)LUÔN LUÔN viết nbyte!)
Christoph

2

Các chức năng này đã phát triển nhiều hơn là được thiết kế, vì vậy thực sự không có "tại sao". Bạn chỉ phải học "cách". Thật không may, ít nhất các trang người đàn ông linux không có các ví dụ trường hợp sử dụng phổ biến cho các hàm này và tôi đã nhận thấy rất nhiều trường hợp sử dụng sai trong mã mà tôi đã xem xét. Tôi đã thực hiện một số ghi chú ở đây: http://www.pixelbeat.org/programming/gcc/string_buffers.html


Erm tại sao _ được chuyển thành% 5F trong URL ở trên? Điểm dưới là tốt theo RFC 3548.
pixelbeat

Giả sử strncpy()như nó tồn tại, người ta có thể buộc chuỗi được kết thúc bằng 0 bằng cách ghi thủ công byte 0 ở cuối bộ đệm. Ngược lại, nếu strncpy () khăng khăng luôn viết byte 0 theo sau vị trí hữu ích cuối cùng, tôi không thể nghĩ ra bất kỳ cách hiệu quả nào để cập nhật chuỗi không đệm (không kết thúc). Lưu ý rằng các chuỗi không đệm có độ dài cố định đã biết đã và vẫn là phương tiện lưu trữ dữ liệu trên đĩa hiệu quả về thời gian; lưu trữ thông tin trong RAM ở định dạng giống như trên đĩa cũng có thể tăng hiệu suất.
supercat

2

Không dựa vào các tiện ích mở rộng mới hơn, trước đây tôi đã làm một việc như thế này:

và có lẽ thậm chí:

Tại sao macro thay vì các hàm "tích hợp sẵn" (?) Mới hơn? Bởi vì đã từng có khá nhiều liên kết khác nhau, cũng như các môi trường không phải đơn nguyên (không phải cửa sổ) khác mà tôi phải chuyển qua lại khi tôi làm C hàng ngày.

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.