ทำไมถึงเป็น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