2011-09-06 51 views
27

Tôi muốn biết đó của hai lựa chọn này là một trong những an toàn hơn để sử dụng:Sprintf/snprintf nào an toàn hơn?

#define MAXLEN 255 
char buff[MAXLEN + 1] 
  1. sprintf(buff, "%.*s", MAXLEN, name)

  2. snprintf(buff, MAXLEN, "%s", name)

sự hiểu biết của tôi là cả hai đều giống nhau . Xin đề nghị.

+0

Thay đổi # 2 thành 'MAXLEN + 1' và chúng sẽ giống hệt với những gì chúng viết vào' buff' trong mọi trường hợp (giá trị trả về sẽ khác nếu 'strlen (name)> 255'). –

Trả lời

0

Cả hai sẽ cho kết quả bạn muốn, nhưng snprintf là tổng quát hơn và sẽ bảo vệ chuỗi của bạn không bị vượt quá bất kể chuỗi định dạng được cung cấp.

Ngoài ra, vì snprintf (hoặc sprintf cho vấn đề đó) thêm \0 cuối cùng, bạn nên làm cho bộ đệm chuỗi lớn hơn một byte, char buff[MAXLEN + 1].

+1

Điều đó hóa ra không phải là trường hợp. Từ tài liệu snprintf: "Hàm snprintf() và vsnprintf() ghi ở hầu hết các byte kích thước (bao gồm cả byte null kết thúc ('\ 0')) đến str." – Chriszuma

25

Hai biểu thức bạn đã cung cấp là không tương đương: sprintf không có đối số nào chỉ định số byte tối đa cần ghi; nó chỉ đơn giản là lấy một bộ đệm đích, một chuỗi định dạng và một loạt các đối số. Vì vậy, nó có thể viết nhiều byte hơn bộ đệm của bạn có không gian cho, và do đó làm viết mã tùy ý. %.*s không phải là giải pháp thỏa đáng vì:

  1. Khi định dạng số tham chiếu đề cập đến độ dài, nó đề cập đến tương đương với strlen; đây là số đo về số ký tự trong chuỗi, không phải là số ký tự trong bộ nhớ (nghĩa là nó không tính toán terminator null).
  2. Bất kỳ thay đổi nào trong chuỗi định dạng (thêm một dòng mới, ví dụ) sẽ thay đổi hành vi của phiên bản sprintf đối với tràn bộ đệm. Với snprintf, mức tối đa cố định, rõ ràng được đặt bất kể thay đổi trong chuỗi định dạng hoặc loại đầu vào.
+0

Trên thực tế, khoảng 1, tôi đã nhầm - nó chỉ định số ký tự tối đa (nhưng không đếm '\ 0'). Tôi đã chỉnh sửa câu trả lời của mình cho phù hợp. –

+0

Cảm ơn - Tôi nhớ đã có một số vấn đề với định dạng đó, và giả sử bạn đã đúng :-P – azernik

+1

Vâng, tất cả điều này phụ thuộc vào loại bảo mật mà câu hỏi của OP là gì. Chính thức một 'sprintf' được sử dụng chính xác là an toàn trong trường hợp cụ thể này là' snprintf'. Những gì bạn đang nói về trong câu trả lời này là thiếu sự bảo vệ từ lập trình lười biếng/không đủ năng lực. Liệu OP có hỏi về khía cạnh an ninh này mà tôi không biết. – AnT

2

Tuyên bố chạy nước rút của bạn là chính xác, nhưng tôi không đủ tự tin để sử dụng cho mục đích an toàn (ví dụ: thiếu một cryptic char và bạn shieldless) trong khi có snprintf xung quanh có thể được áp dụng cho mọi định dạng ... oh chờ snprintf không có trong ANSI C. Đó là (chỉ?) C99. Đó có thể là một lý do (yếu) để thích cái kia.

Vâng. Bạn cũng có thể sử dụng strncpy, phải không?

ví dụ:

char buffer[MAX_LENGTH+1]; 
    buffer[MAX_LENGTH]=0;    // just be safe in case name is too long 
    strncpy(buffer,MAX_LENGTH,name); // strncpy will never overwrite last byte 
+0

Và bạn có chắc chắn rằng '%. * S' có sẵn trong ANSI C không? Tôi đã cố gắng để tìm các đặc điểm kỹ thuật, nhưng chỉ có thể tìm thấy một tham chiếu (không đáng tin cậy) mà không chỉ định '. *'. –

+0

@Eli: Chỉ định độ chính xác (như ở đây) hoặc độ rộng trường là dấu hoa thị đã ở trong ANSI C kể từ tiêu chuẩn ban đầu (1989/1990). 'snprintf()' đã được thêm vào trong tiêu chuẩn C99. –

+0

@Michael - cho chuỗi ('% s')? Đó là điều tốt để biết, cảm ơn. –

8

Ví dụ đơn giản trong câu hỏi, có thể không có nhiều khác biệt về bảo mật giữa hai cuộc gọi. Tuy nhiên, trong trường hợp chung, snprintf() có thể an toàn hơn. Khi bạn có chuỗi định dạng phức tạp hơn với nhiều thông số chuyển đổi, có thể khó (hoặc gần không thể) để đảm bảo rằng bạn có chiều dài bộ đệm được tính chính xác trên các chuyển đổi khác nhau - đặc biệt là do chuyển đổi trước đó không nhất thiết tạo ra số cố định các ký tự đầu ra.

Vì vậy, tôi muốn gắn bó với snprintf().

Một lợi thế nhỏ khác là snprintf() (mặc dù không liên quan đến bảo mật) là nó sẽ cho bạn biết dung lượng bộ đệm bạn cần.

Một lưu ý cuối cùng - bạn nên xác định kích thước bộ đệm thực tế trong snprintf() cuộc gọi - nó sẽ xử lý chiếm terminator null cho bạn:

snprintf(buff, sizeof(buff), "%s", name); 
2

tôi sẽ nói snprintf() là tốt hơn nhiều hơn nữa cho đến khi tôi đọc đoạn này:

https://buildsecurityin.us-cert.gov/bsi/articles/knowledge/coding/838-BSI.html

tóm tắt ngắn là: snprintf() không di động của nó thay đổi hành vi theo hệ thống. Vấn đề nghiêm trọng nhất với snprintf() có thể xảy ra khi snprintf() được thực hiện đơn giản bằng cách gọi sprintf().Bạn có thể nghĩ rằng nó bảo vệ bạn khỏi tràn bộ đệm và để bảo vệ của bạn không hoạt động, nhưng có thể không.

Vì vậy, bây giờ tôi vẫn đang nói snprintf() an toàn hơn nhưng cũng thận trọng khi tôi sử dụng nó.

1

Cách tốt nhất và linh hoạt nhất là sử dụng snprintf!

size_t nbytes = snprintf(NULL, 0, "%s", name) + 1; /* +1 for the '\0' */ 
char *str = malloc(nbytes); 
snprintf(str, nbytes, "%s", name); 

Trong C99, snprintf trả về số byte ghi vào chuỗi trừ '\0'. Nếu có ít hơn số byte cần thiết, snprintf trả về số byte cần thiết để mở rộng định dạng (vẫn không bao gồm '\0'). Bằng cách vượt qua snprintf một chuỗi có độ dài 0, bạn có thể tìm hiểu trước thời gian chuỗi mở rộng sẽ có và sử dụng nó để cấp phát bộ nhớ cần thiết.

1

Có sự khác biệt quan trọng giữa hai lệnh này - cuộc gọi snprintf sẽ quét đối số name đến cuối (chấm dứt NUL) để tìm ra giá trị trả lại chính xác. Các cuộc gọi sprintf mặt khác sẽ đọc AT MOST 255 ký tự từ name. Vì vậy, nếu name là con trỏ đến bộ đệm không có NUL có ít nhất 255 ký tự, cuộc gọi snprintf có thể chạy hết bộ đệm và kích hoạt hành vi không xác định (chẳng hạn như lỗi), trong khi phiên bản sprintf sẽ không .

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