2009-09-21 45 views
59

strncpy() được cho là bảo vệ khỏi tràn bộ đệm. Nhưng nếu nó ngăn chặn một tràn mà không có null chấm dứt, trong tất cả các khả năng một chuỗi tiếp theo hoạt động sẽ tràn. Vì vậy, để bảo vệ chống lại điều này tôi thấy mình thực hiện:Tại sao strncpy không null chấm dứt?

strncpy(dest, src, LEN); 
dest[LEN - 1] = '\0'; 

man strncpy cho:

Các strncpy() chức năng tương tự, ngoại trừ việc không quá n byte src được sao chép. Do đó, nếu không có byte rỗng trong số n byte đầu tiên của src, kết quả sẽ không bị hủy.

Without null kết thúc một cái gì đó dường như vô tội như:

printf("FOO: %s\n", dest); 

... có thể sụp đổ.


Có các phương án thay thế an toàn hơn, tốt hơn cho strncpy()?

+1

Lưu ý rằng trên MacOS X (BSD) trang người đàn ông nói (của ''extern char * strncpy (char * giới hạn s1, const char * hạn chế s2, size_t n);' '): Hàm strncpy() sao chép tại hầu hết n ký tự từ s2 vào s1.Nếu s2 dài hơn n ký tự, phần còn lại của s1 được điền bằng các ký tự '\ 0 '. Nếu không, s1 không bị chấm dứt. –

+0

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

+1

Đây là cách tôi nghĩ chúng tôi 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); dest [LEN] = '\ 0'; – codeObserver

Trả lời

34

strncpy không được dùng để an toàn hơn strcpy, nó được cho là được sử dụng để chèn một chuỗi ở giữa một chuỗi khác.

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

Wikipedia đề cập strncat như một thay thế cho văn bản của riêng bạn an toàn strncpy:

*dst = '\0'; strncat(dst, src, LEN); 

EDIT

tôi đã bỏ lỡ strncat vượt quá LEN ký tự khi null chấm dứt chuỗi nếu nó dài hơn hoặc bằng LEN char's.

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

Tất nhiên bạn cần phải kiểm tra dst chứa ít nhất là nullchar, vì vậy việc sử dụng đúng đắn về strncat sẽ là một cái gì đó như:

if(LEN) { *dst = '\0'; strncat(dst, src, LEN-1); } 

Tôi cũng admitt strncpy đó không phải là rất hữu ích cho việc sao chép một chuỗi 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.

+20

" nó được cho là được sử dụng để chèn một chuỗi ở giữa một chuỗi khác "- không, nó có ý định viết một chuỗi vào một trường có chiều 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ỉ nếu) chuỗi nguồn quá ngắn. –

+3

Làm cách nào để đặt * dst = '\ 0' làm việc này an toàn hơn? Nó vẫn có vấn đề ban đầu cho phép bạn viết vượt ra ngoài phần cuối của bộ đệm đích. –

+0

Bởi vì anh ta sử dụng 'strncat', mà * nối * vào chuỗi đích. –

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ạn PHẢI là kích thước bộ đệm và bạn có thể đặt thủ công '\ 0'.

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

8

Một số giải pháp 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 lấy một tham số bổ sung chỉ định độ dài tối đa của biến mục tiêu, đảm bảo rằng tất cả các chuỗi đều được kết thúc bằng null và có các tên kết thúc bằng _s (để "an toàn") để phân biệt chúng với "không an toàn" trước đó của chúng. phiên bản.

Thật không may, họ vẫn nhận được hỗ trợ và có thể không có sẵn với bộ công cụ cụ thể của bạn. Các phiên bản sau của Visual Studio sẽ ném cảnh báo nếu bạn sử dụng các chức năng không an toàn cũ.

Nếu công cụ của bạn không hỗ trợ các chức năng mới, 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. Dưới đây là ví dụ:

errCode_t strncpy_safe(char *sDst, size_t lenDst, 
         const char *sSrc, size_t count) 
{ 
    // No NULLs allowed. 
    if (sDst == NULL || sSrc == NULL) 
     return ERR_INVALID_ARGUMENT; 

    // Validate buffer space. 
    if (count >= lenDst) 
     return ERR_BUFFER_OVERFLOW; 

    // Copy and always null-terminate 
    memcpy(sDst, sSrc, count); 
    *(sDst + count) = '\0'; 

    return OK; 
} 

Bạn có thể thay đổi chức năng cho phù hợp với nhu cầu của mình, ví dụ như luôn sao chép càng nhiều chuỗi càng tốt mà không bị tràn. Trong thực tế, việc thực hiện VC++ có thể làm điều này nếu bạn vượt qua _TRUNCATE làm count.




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

+0

Bạn không thể xác định một cách hợp pháp một chức năng có tên bắt đầu bằng str *, "không gian tên" được bảo lưu trong C. – unwind

+2

Nhưng ủy ban ISO C có thể - và . Xem thêm: http://stackoverflow.com/questions/372980/do-you-use-the-tr-24731-safe-functions-in-your-c-code –

+0

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

3

Tôi luôn ưa thích:

memset(dest, 0, LEN); 
strncpy(dest, src, LEN - 1); 

để sửa chữa nó lên sau đó tiếp cận, nhưng đó thực sự chỉ là một vấn đề sở thích.

+4

Một hoạt động đắt hơn nhiều, * đặc biệt là * cho src nhỏ và LEN lớn ... – DevSolar

+0

Liệu có nên khởi tạo tất cả các vùng đệm bằng không là một chủ đề tranh luận theo đúng nghĩa của nó hay không. 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 làm cho các lỗi rõ ràng hơn, nhưng có rất nhiều tùy chọn khác ("rẻ hơn"). –

+6

bạn chỉ cần đặt 'dest [LEN-1]' thành '0' - các byte khác sẽ được lấp đầy bởi' strncpy() 'nếu cần (nhớ:' strncpy (s, d, n) 'ALWAYS viết' n 'bytes!) – Christoph

19

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

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

Trong tham chiếu có liên kết đến nguồn.

+5

+1 strlcpy là strcpy an toàn thực sự, không strncpy – kmm

+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à đơn đặt hàng của cường độ thấp hơn.IMO, strncpy sẽ không được dùng nữa và được thay thế bằng cùng hàm gọi là dirnamecpy hoặc một cái gì đó tương tự. strncpy không phải là chuỗi sao chép an toàn và chưa bao giờ được. – jbcreix

17

Ban đầu, hệ thống tệp 7th Edition UNIX (xem DIR (5)) có các mục nhập thư mục giới hạn tên tệp là 14 byte; mỗi mục trong một thư mục bao gồm 2 byte cho số inode cộng với 14 byte cho tên, null đệm đến 14 ký tự, nhưng không nhất thiết phải null chấm dứt. Đó là niềm tin của tôi rằng strncpy() được thiết kế để hoạt động với những 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:

  • Một tên file 14 ký tự đã không kết thúc vô.
  • Nếu tên có độ dài nhỏ hơn 14 byte, nó không được đệm thành độ dài đầy đủ (14 byte).

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

strncpy(inode->d_name, filename, 14); 

Vì vậy, strncpy() được lý tưởng gắn với ứng dụng thích hợp ban đầu của nó. Nó chỉ là ngẫu nhiên về ngăn chặn tràn của null-chấm dứt chuỗi.

(Lưu ý rằng đệm rỗng 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 20 ký tự vào đó một cách an toàn, thì thêm 4075 null là quá mức nghiêm trọng , và có thể dễ dàng dẫn đến hành vi bậc hai nếu bạn đang liên tục bổ sung thêm tài liệu để một bộ đệm dài)

+0

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

5

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

Nếu libc của bạn không có một thực hiện, sau đó thử cái này :

size_t strlcpy(char* dst, const char* src, size_t bufsize) 
{ 
    size_t srclen =strlen(src); 
    size_t result =srclen; /* Result is always the length of the src string */ 
    if(bufsize>0) 
    { 
    if(srclen>=bufsize) 
     srclen=bufsize-1; 
    if(srclen>0) 
     memcpy(dst,src,srclen); 
    dst[srclen]='\0'; 
    } 
    return result; 
} 

(Được viết bởi tôi vào năm 2004 - dành riêng cho miền công cộng.)

2

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

+0

Erm tại sao _ bị cắt xén thành% 5F trong URL ở trên? Dấu gạch dưới là tốt theo RFC 3548. – pixelbeat

+0

Với 'strncpy()' vì nó tồn tại, người ta có thể buộc chuỗi không bị chấm dứt bằng cách viết thủ công byte không ở cuối bộ đệm. Ngược lại, nếu strncpy() nhấn mạnh khi luôn viết một byte không theo 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 các chuỗi không đệm (không bị chấm dứt). Lưu ý rằng các chuỗi không có đệm có độ dài cố định đã biết là và vẫn là một phương tiện lưu trữ dữ liệu hiệu quả về thời gian trên đĩa; lưu trữ thông tin trong RAM với cùng định dạng như trên đĩa cũng có thể tăng hiệu suất. – supercat

3

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

snprintf(buffer, BUFFER_SIZE, "%s", src); 

Dưới đây là một lớp lót mà các bản sao ở hầu hết các size-1 ký tự không null từ src để dest và thêm một null terminator:

static inline void cpystr(char *dest, const char *src, size_t size) 
{ if(size) while((*dest++ = --size ? *src++ : 0)); } 
+0

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ó trên các điểm đến char * – che

6

strncpy là an toàn hơn so với các cuộc tấn công tràn ngăn xếp bởi người sử dụng của chương trình của bạn, nó không bảo vệ bạn chống lại lỗi bạn lập trình viên làm, chẳng hạn như in một chuỗi không bị vô hiệu, theo cách bạn đã mô tả.

Bạn có thể tránh đâm từ vấn đề bạn đã mô tả bằng cách giới hạn số ký tự in bằng printf:

char my_string[10]; 
//other code here 
printf("%.9s",my_string); //limit the number of chars to be printed to 9 
+0

Việc sử dụng trường chính xác để giới hạn số ký tự được in bởi '% s' phải là một trong những tính năng tối nghĩa nhất của C. –

+0

@DavidThornley Nó được ghi chép rất rõ ràng trong K & R theo sprintf. – weston

+0

@weston: Và tại Harbison & Steele, đó là những gì tôi có ở đây tại nơi làm 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 này, điều này có được đề cập không? 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 của sự tối tăm thì không có tính năng tối nghĩa. –

1

Nếu không dựa vào phần mở rộng mới hơn, tôi đã làm một cái gì đó như thế này trong quá khứ:

/* copy N "visible" chars, adding a null in the position just beyond them */ 
#define MSTRNCPY(dst, src, len) (strncpy((dst), (src), (len)), (dst)[ (len) ] = '\0') 

và thậm chí:

/* pull up to size - 1 "visible" characters into a fixed size buffer of known size */ 
#define MFBCPY(dst, src) MSTRNCPY((dst), (src), sizeof(dst) - 1) 

Tại sao các macro thay vì Newe r "được xây dựng trong" (?) chức năng? Bởi vì có sử dụng được khá một vài khác biệt unices, cũng như không unix unix (non-windows) môi trường mà tôi đã phải cổng để trở lại khi tôi đã làm C trên một cơ sở hàng ngày.

Các vấn đề liên quan