ทำไมถึงเป็นgets()
อันตราย
หนอนอินเทอร์เน็ตตัวแรก ( Morris Internet Worm ) หนีไปเมื่อประมาณ 30 ปีที่แล้ว (1988-11-02) และมันใช้gets()
และบัฟเฟอร์ล้นเป็นหนึ่งในวิธีการแพร่กระจายจากระบบหนึ่งสู่อีกระบบหนึ่ง ปัญหาพื้นฐานคือฟังก์ชั่นไม่ทราบว่าบัฟเฟอร์มีขนาดใหญ่เท่าใดดังนั้นจึงอ่านต่อไปจนกว่าจะพบบรรทัดใหม่หรือพบ EOF และอาจล้นเกินขอบเขตของบัฟเฟอร์ที่ได้รับ
คุณควรลืมคุณเคยได้ยินสิ่งที่gets()
มีอยู่
มาตรฐาน C11 ISO / IEC 9899: 2011 ตัดออกgets()
เป็นฟังก์ชั่นมาตรฐานซึ่งเป็นสิ่งที่ดี™ (มันถูกทำเครื่องหมายอย่างเป็นทางการว่า 'ล้าสมัย' และ 'เลิกใช้แล้ว' ใน ISO / IEC 9899: 1999 / Cor.3: 2007 - Corrigendum ทางเทคนิค 3 สำหรับ C99 และลบออกใน C11) น่าเศร้าที่มันยังคงอยู่ในห้องสมุดเป็นเวลาหลายปี (หมายถึง 'ทศวรรษ') ด้วยเหตุผลของความเข้ากันได้ย้อนหลัง ถ้ามันขึ้นอยู่กับฉันการดำเนินการของgets()
จะกลายเป็น:
char *gets(char *buffer)
{
assert(buffer != 0);
abort();
return 0;
}
ระบุว่ารหัสของคุณจะขัดข้องไม่ช้าก็เร็วจะดีกว่าที่จะแก้ปัญหาให้เร็วกว่าในภายหลัง ฉันพร้อมที่จะเพิ่มข้อความแสดงข้อผิดพลาด:
fputs("obsolete and dangerous function gets() called\n", stderr);
ระบบคอมไพล์ Linux รุ่นใหม่สร้างคำเตือนหากคุณลิงค์gets()
- และสำหรับฟังก์ชั่นอื่น ๆ ที่มีปัญหาด้านความปลอดภัย ( mktemp()
, ... )
ทางเลือกในการ gets()
fgets ()
ดังที่คนอื่น ๆ บอกกันแล้วทางเลือกที่ยอมรับได้gets()
คือการfgets()
ระบุstdin
ว่าเป็นสตรีมไฟล์
char buffer[BUFSIZ];
while (fgets(buffer, sizeof(buffer), stdin) != 0)
{
...process line of data...
}
สิ่งที่ไม่มีใครอื่น ๆ ที่กล่าวถึงก็คือว่าgets()
ไม่รวมถึงการขึ้นบรรทัดใหม่ แต่fgets()
ไม่ ดังนั้นคุณอาจต้องใช้ wrapper รอบ ๆfgets()
เพื่อลบบรรทัดใหม่:
char *fgets_wrapper(char *buffer, size_t buflen, FILE *fp)
{
if (fgets(buffer, buflen, fp) != 0)
{
size_t len = strlen(buffer);
if (len > 0 && buffer[len-1] == '\n')
buffer[len-1] = '\0';
return buffer;
}
return 0;
}
หรือดีกว่า:
char *fgets_wrapper(char *buffer, size_t buflen, FILE *fp)
{
if (fgets(buffer, buflen, fp) != 0)
{
buffer[strcspn(buffer, "\n")] = '\0';
return buffer;
}
return 0;
}
นอกจากนี้ในขณะที่คาเฟ่ชี้ให้เห็นในความคิดเห็นและpaxdiabloแสดงในคำตอบของเขากับfgets()
คุณอาจมีข้อมูลที่เหลืออยู่ในบรรทัด รหัส wrapper ของฉันทำให้ข้อมูลนั้นถูกอ่านในครั้งถัดไป คุณสามารถแก้ไขได้อย่างง่ายดายเพื่อฮุบส่วนที่เหลือของสายข้อมูลถ้าคุณต้องการ:
if (len > 0 && buffer[len-1] == '\n')
buffer[len-1] = '\0';
else
{
int ch;
while ((ch = getc(fp)) != EOF && ch != '\n')
;
}
ปัญหาที่เหลือคือวิธีการรายงานสถานะผลลัพธ์สามแบบ - EOF หรือข้อผิดพลาดการอ่านบรรทัดและไม่ถูกตัดทอนและการอ่านบรรทัดบางส่วน แต่ข้อมูลถูกตัดทอน
ปัญหานี้ไม่ได้เกิดขึ้นกับgets()
เพราะไม่ทราบว่าปลายกันชนของคุณและกระทืบเท้าอย่างสนุกสนานเกินปลายอลหม่านในรูปแบบหน่วยความจำของคุณมีแนวโน้มที่สวยงามมักจะล้อขึ้นผลตอบแทนสแต็ค (เป็นกองมากเกิน ) ถ้าบัฟเฟอร์จะถูกจัดสรรบน สแต็คหรือการเหยียบย่ำข้อมูลการควบคุมหากมีการจัดสรรบัฟเฟอร์แบบไดนามิกหรือคัดลอกข้อมูลผ่านตัวแปร (หรือโมดูล) ที่มีค่าอื่น ๆ ทั่วโลกหากบัฟเฟอร์ได้รับการจัดสรรแบบคงที่ สิ่งเหล่านี้ไม่มีความคิดที่ดี - พวกเขาเป็นตัวอย่างที่ชัดเจนของวลี 'พฤติกรรมที่ไม่ได้กำหนด'
นอกจากนี้ยังมีTR 24731-1 (รายงานทางเทคนิคจากคณะกรรมการมาตรฐาน C) ซึ่งให้ทางเลือกที่ปลอดภัยกว่าสำหรับฟังก์ชั่นที่หลากหลายรวมถึงgets()
:
§6.5.4.1 gets_s
ฟังก์ชั่น
สรุป
#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
char *gets_s(char *s, rsize_t n);
Runtime- จำกัด
s
จะต้องไม่เป็นตัวชี้โมฆะ n
จะต้องไม่เท่ากับศูนย์หรือมากกว่า RSIZE_MAX ตัวละครใหม่สายสิ้นสุดของแฟ้มหรือข้อผิดพลาดในการอ่านให้เกิดขึ้นภายในการอ่าน
ตัวอักษรจากn-1
25)stdin
3 หากมีการละเมิดข้อ จำกัด รันไทม์s[0]
ถูกตั้งค่าเป็นอักขระโมฆะและตัวอักษรจะถูกอ่านและทิ้งstdin
จนกว่าจะมีการอ่านอักขระบรรทัดใหม่หรือสิ้นสุดไฟล์หรือเกิดข้อผิดพลาดในการอ่าน
ลักษณะ
4 gets_s
ฟังก์ชั่นอ่านมากที่สุดคนหนึ่งน้อยกว่าจำนวนตัวอักษรที่ระบุโดยn
จากกระแสที่ชี้ไปตามลงไปในอาร์เรย์ที่ชี้ไปตามstdin
s
ไม่มีการอ่านอักขระเพิ่มเติมหลังอักขระบรรทัดใหม่ (ซึ่งถูกละทิ้ง) หรือหลังสิ้นสุดไฟล์ อักขระขึ้นบรรทัดใหม่ที่ถูกทิ้งจะไม่นับรวมตามจำนวนอักขระที่อ่าน อักขระ null ถูกเขียนทันทีหลังจากอ่านอักขระสุดท้ายลงในอาร์เรย์
5 หากพบจุดสิ้นสุดไฟล์และไม่มีการอ่านอักขระในอาเรย์หรือหากเกิดข้อผิดพลาดในการอ่านระหว่างการดำเนินการs[0]
จะถูกตั้งค่าเป็นอักขระโมฆะและองค์ประกอบอื่น ๆ ของs
ค่าที่ไม่ระบุ
วิธีปฏิบัติที่แนะนำ
6 fgets
ฟังก์ชั่นอนุญาตให้โปรแกรมที่เขียนอย่างถูกต้องประมวลผลบรรทัดอินพุตนานเกินไปที่จะเก็บไว้ในอาร์เรย์ผลลัพธ์ โดยทั่วไปสิ่งนี้กำหนดให้ผู้โทรที่fgets
ต้องใส่ใจกับการมีหรือไม่มีอักขระบรรทัดใหม่ในอาร์เรย์ผลลัพธ์ พิจารณาใช้fgets
(พร้อมกับการประมวลผลที่จำเป็นใด ๆ ที่อยู่บนพื้นฐานของตัวละครใหม่เส้น)
gets_s
แทน
25)gets_s
ฟังก์ชั่นไม่เหมือนgets
ทำให้มันมีการละเมิดรันไทม์ จำกัด สำหรับสายของการป้อนข้อมูลไปยังหน่วยความจำล้นเก็บไว้ ซึ่งแตกต่างจากfgets
, รักษาความสัมพันธ์แบบหนึ่งต่อหนึ่งระหว่างเส้นที่นำเข้าและสายที่ประสบความสำเร็จgets_s
gets_s
โปรแกรมที่ใช้gets
คาดหวังความสัมพันธ์ดังกล่าว
คอมไพเลอร์ Microsoft Visual Studio ใช้การประมาณมาตรฐาน TR 24731-1 แต่มีความแตกต่างระหว่างลายเซ็นที่ใช้โดย Microsoft และที่อยู่ใน TR
มาตรฐาน C11, ISO / IEC 9899-2011 รวมถึง TR24731 ในภาคผนวก K เป็นส่วนเสริมของห้องสมุด น่าเสียดายที่มันไม่ค่อยมีการใช้งานบนระบบที่เหมือน Unix
getline()
- POSIX
POSIX 2008 นอกจากนี้ยังมีทางเลือกที่ปลอดภัยที่จะเรียกว่าgets()
getline()
มันจัดสรรพื้นที่สำหรับบรรทัดแบบไดนามิกดังนั้นคุณจะต้องมีอิสระ จะลบข้อ จำกัด เกี่ยวกับความยาวบรรทัดดังนั้น นอกจากนี้ยังส่งคืนความยาวของข้อมูลที่อ่านหรือ-1
(และไม่ใช่EOF
!) ซึ่งหมายความว่าไบต์ว่างในอินพุตสามารถจัดการได้อย่างน่าเชื่อถือ นอกจากนี้ยังมีรูปแบบ 'เลือกเองคั่นตัวเดียวของคุณเรียกว่าgetdelim()
; สิ่งนี้มีประโยชน์หากคุณกำลังจัดการกับเอาต์พุตจากfind -print0
จุดสิ้นสุดของชื่อไฟล์ที่ถูกทำเครื่องหมายด้วยอักขระ ASCII NUL '\0'
ตัวอย่างเช่น
gets()
Buffer_overflow_attack