ทำไมรหัสนี้มีความเสี่ยงที่จะบัฟเฟอร์การโจมตีล้น?


148
int func(char* str)
{
   char buffer[100];
   unsigned short len = strlen(str);

   if(len >= 100)
   {
        return (-1);
   }

   strncpy(buffer,str,strlen(str));
   return 0;
}

รหัสนี้มีความเสี่ยงต่อการโจมตีแบบ buffer overflow และฉันพยายามหาสาเหตุ ฉันคิดว่ามันเกี่ยวกับlenการได้รับการประกาศshortแทนที่จะเป็นintแต่ฉันไม่แน่ใจจริงๆ

ความคิดใด ๆ


3
รหัสนี้มีหลายปัญหา จำได้ว่าสตริง C สิ้นสุดด้วยค่า null
Dmitri Chubarov

4
@DmitriChubarov strncpyไม่เป็นโมฆะยุติสตริงจะเป็นปัญหาเฉพาะในกรณีที่สตริงถูกนำมาใช้หลังจากที่โทรไป ในกรณีนี้มันไม่ได้
R Sahu

43
ปัญหาในการไหลของรหัสนี้โดยตรงจากความจริงที่ได้strlenรับการคำนวณใช้สำหรับการตรวจสอบความถูกต้องและจากนั้นจะมีการคำนวณอย่างไร้เหตุผลอีกครั้ง - มันเป็นความล้มเหลวแห้ง ถ้าสองstrlen(str)ถูกแทนที่ด้วยจะมีความเป็นไปได้ของหน่วยความจำล้นไม่คำนึงถึงประเภทของlen lenคำตอบไม่ได้อยู่ที่จุดนี้พวกเขาเพียงจัดการเพื่อหลีกเลี่ยง
Jim Balter

3
@CiaPan: Wenn ส่งผ่านสตริงที่ไม่สิ้นสุดด้วย null strlen จะแสดงพฤติกรรมที่ไม่ได้กำหนด
Kaiserludi

3
@ JimBalter Nah ฉันคิดว่าฉันจะทิ้งพวกเขาไว้ที่นั่น บางทีคนอื่นอาจมีความเข้าใจผิดที่โง่เขลาเหมือนกันและเรียนรู้จากมัน อย่าลังเลที่จะตั้งค่าสถานะพวกเขาหากพวกเขาทำให้คุณรำคาญบางคนอาจเข้ามาและลบออก
ซาด Saeeduddin

คำตอบ:


192

ในคอมไพเลอร์ส่วนใหญ่ค่าสูงสุดของunsigned shortคือ 65535

ค่าใด ๆ ข้างต้นที่ล้อมรอบดังนั้น 65536 กลายเป็น 0 และ 65600 กลายเป็น 65

ซึ่งหมายความว่าสายยาวที่มีความยาวถูกต้อง (เช่น 65600) จะผ่านการตรวจสอบและล้นบัฟเฟอร์


ใช้size_tเพื่อเก็บผลลัพธ์strlen()ไม่ใช่unsigned shortและเปรียบเทียบlenกับนิพจน์ที่เข้ารหัสขนาดbufferโดยตรง ตัวอย่างเช่น:

char buffer[100];
size_t len = strlen(str);
if (len >= sizeof(buffer) / sizeof(buffer[0]))  return -1;
memcpy(buffer, str, len + 1);

2
@PatrickRoberts ในทางทฤษฎีใช่ แต่คุณต้องจำไว้ว่า 10% ของรหัสรับผิดชอบ 90% ของรันไทม์ดังนั้นคุณไม่ควรปล่อยให้ประสิทธิภาพไปถึงก่อนที่จะปลอดภัย และโปรดทราบว่าเมื่อเวลาผ่านไปการเปลี่ยนแปลงรหัสซึ่งอาจหมายถึงการตรวจสอบก่อนหน้านี้หายไป
orlp

3
เพื่อป้องกันการโอเวอร์lenโฟลว์บัฟเฟอร์ให้ใช้เป็นอาร์กิวเมนต์ที่สามของ strncpy การใช้ strlen อีกครั้งจะเป็นใบ้ในทุกกรณี
Jim Balter

15
/ sizeof(buffer[0])- โปรดทราบว่าsizeof(char)ใน C คือ1 เสมอ (แม้ว่า char จะมีบิต gazillion) ดังนั้นมันจึงไม่จำเป็นเมื่อใช้ชนิดข้อมูลอื่น ยัง ... รุ่งโรจน์สำหรับคำตอบที่สมบูรณ์ (และขอบคุณสำหรับการตอบสนองต่อความคิดเห็น)
Jim Balter

3
@ rr-: char[]และchar*ไม่เหมือนกัน มีหลายสถานการณ์ในการที่จะได้รับการแปลงเป็นโดยปริยายchar[] char*ตัวอย่างเช่นchar[]ตรงกับchar*เมื่อใช้เป็นประเภทสำหรับอาร์กิวเมนต์ของฟังก์ชัน sizeof()อย่างไรก็ตามการแปลงไม่เกิดขึ้น
Dietrich Epp

4
@Controll เพราะถ้าคุณเปลี่ยนขนาดของbufferบางจุดนิพจน์จะอัพเดตโดยอัตโนมัติ นี่เป็นสิ่งสำคัญสำหรับความปลอดภัยเนื่องจากการประกาศbufferอาจมีบางบรรทัดห่างจากการตรวจสอบในรหัสจริง ดังนั้นจึงง่ายต่อการเปลี่ยนขนาดของบัฟเฟอร์ แต่อย่าลืมอัปเดตในทุกตำแหน่งที่มีการใช้ขนาด
orlp

28

ปัญหาอยู่ที่นี่:

strncpy(buffer,str,strlen(str));
                   ^^^^^^^^^^^

หากสตริงมากกว่าความยาวของบัฟเฟอร์เป้าหมาย strncpy จะยังคงคัดลอกไป คุณกำลังอ้างอิงจำนวนอักขระของสตริงเป็นหมายเลขที่จะคัดลอกแทนขนาดของบัฟเฟอร์ วิธีที่ถูกต้องในการทำเช่นนี้มีดังนี้:

strncpy(buffer,str, sizeof(buff) - 1);
buffer[sizeof(buff) - 1] = '\0';

สิ่งนี้ทำคือ จำกัด จำนวนข้อมูลที่คัดลอกไปยังขนาดที่แท้จริงของบัฟเฟอร์ลบหนึ่งสำหรับอักขระที่ยกเลิก null จากนั้นเราจะตั้งค่าไบต์สุดท้ายในบัฟเฟอร์ให้เป็นอักขระ null เพื่อเป็นการป้องกันเพิ่มเติม เหตุผลนี้เป็นเพราะ strncpy จะคัดลอกไม่เกิน n ไบต์รวมถึงการยกเลิก null ถ้า strlen (str) <len - 1 ถ้าไม่เช่นนั้น null จะไม่ถูกคัดลอกและคุณมีสถานการณ์ความผิดพลาดเพราะตอนนี้บัฟเฟอร์ของคุณมีการทำลาย เชือก

หวังว่านี่จะช่วยได้

แก้ไข: เมื่อตรวจสอบเพิ่มเติมและป้อนข้อมูลจากผู้อื่นการเข้ารหัสที่เป็นไปได้สำหรับฟังก์ชั่นดังต่อไปนี้:

int func (char *str)
  {
    char buffer[100];
    unsigned short size = sizeof(buffer);
    unsigned short len = strlen(str);

    if (len > size - 1) return(-1);
    memcpy(buffer, str, len + 1);
    buffer[size - 1] = '\0';
    return(0);
  }

เนื่องจากเราทราบความยาวของสตริงเราจึงสามารถใช้ memcpy เพื่อคัดลอกสตริงจากตำแหน่งที่อ้างอิงโดย str ลงในบัฟเฟอร์ โปรดทราบว่าในหน้าคู่มือสำหรับ strlen (3) (บนระบบ FreeBSD 9.3) จะมีการระบุสิ่งต่อไปนี้:

 The strlen() function returns the number of characters that precede the
 terminating NUL character.  The strnlen() function returns either the
 same result as strlen() or maxlen, whichever is smaller.

ซึ่งฉันตีความว่าเป็นความยาวของสตริงที่ไม่รวมถึงโมฆะ นั่นคือเหตุผลที่ฉันคัดลอก len + 1 ไบต์เพื่อรวม null และการทดสอบตรวจสอบเพื่อให้แน่ใจว่าความยาว <ขนาดของบัฟเฟอร์ - 2 ลบหนึ่งเพราะบัฟเฟอร์เริ่มต้นที่ตำแหน่ง 0 และลบอีกหนึ่งเพื่อให้แน่ใจว่ามีที่ว่าง สำหรับ null

แก้ไข: ปรากฎขนาดของบางสิ่งเริ่มต้นด้วย 1 ในขณะที่การเข้าถึงเริ่มต้นด้วย 0 ดังนั้น -2 ก่อนหน้านี้ไม่ถูกต้องเพราะมันจะส่งกลับข้อผิดพลาดสำหรับอะไร> 98 ไบต์ แต่ควร> 99 ไบต์

แก้ไข: แม้ว่าคำตอบสั้น ๆ ที่ไม่ได้ลงชื่อจะถูกต้องเนื่องจากความยาวสูงสุดที่สามารถแสดงได้คือ 65,535 ตัวอักษร แต่ก็ไม่สำคัญเพราะหากสตริงยาวกว่านั้นค่าจะตัดไป มันเหมือนกับเอา 75,231 (ซึ่งคือ 0x000125DF) และปิดบัง 16 บิตแรกที่ให้คุณ 9695 (0x000025DF) ปัญหาเดียวที่ฉันเห็นด้วยกับเรื่องนี้เป็น 100 ตัวอักษรแรกที่ผ่านมา 65,535 การตรวจสอบความยาวจะช่วยให้คัดลอกแต่จะคัดลอกขึ้นไป 100 ตัวอักษรแรกของสตริงในทุกกรณีและ null ยุติสตริง ดังนั้นแม้จะมีปัญหาการตัดผ่านบัฟเฟอร์ยังคงไม่ถูกโอเวอร์โฟลว์

สิ่งนี้อาจหรือไม่อาจมีความเสี่ยงด้านความปลอดภัยขึ้นอยู่กับเนื้อหาของสตริงและสิ่งที่คุณใช้งาน หากเป็นเพียงข้อความตรงที่มนุษย์สามารถอ่านได้แสดงว่าไม่มีปัญหา คุณเพิ่งได้รับสตริงที่ถูกตัดทอน อย่างไรก็ตามหากเป็นเหมือน URL หรือแม้แต่ลำดับของคำสั่ง SQL คุณอาจมีปัญหา


2
จริง แต่นั่นอยู่นอกเหนือขอบเขตของคำถาม รหัสแสดงให้เห็นอย่างชัดเจนฟังก์ชั่นที่ถูกส่งผ่านตัวชี้ถ่าน นอกขอบเขตของฟังก์ชั่นเราไม่สนใจ
Daniel Rudy

"บัฟเฟอร์ที่เก็บ str" - นั่นไม่ใช่บัฟเฟอร์ล้นซึ่งเป็นปัญหาที่นี่ และทุกคำตอบก็มี "ปัญหา" ซึ่งหลีกเลี่ยงไม่ได้เนื่องจากลายเซ็นของfunc... และฟังก์ชั่น C อื่น ๆ ที่เคยเขียนทั้งหมดที่ใช้สตริงที่ถูกยกเลิก NUL เป็นอาร์กิวเมนต์ การนำความเป็นไปได้ของการป้อนข้อมูลที่ไม่ถูกยกเลิกด้วย NUL นั้นมีความเป็นไปได้อย่างสมบูรณ์
Jim Balter

"นั่นเกินขอบเขตของคำถาม" - ซึ่งน่าเศร้าเกินกว่าความสามารถของบางคนที่จะเข้าใจ
Jim Balter

"ปัญหาอยู่ที่นี่" - ถูกต้อง แต่คุณยังขาดประเด็นสำคัญซึ่งก็คือการทดสอบ ( len >= 100) ทำกับค่าหนึ่งค่า แต่ความยาวของสำเนาได้รับค่าที่แตกต่าง ... นี่ เป็นการละเมิดหลักการ DRY เพียงแค่โทรstrncpy(buffer, str, len)หลีกเลี่ยงความเป็นไปได้ของหน่วยความจำล้นและทำงานน้อยกว่าstrncpy(buffer,str,sizeof(buffer) - 1)... memcpy(buffer, str, len)แม้ว่านี่จะเป็นเพียงเทียบเท่าช้าลง
Jim Balter

@ JimBalter มันเกินขอบเขตของคำถาม แต่ฉันพูดนอกเรื่อง ฉันเข้าใจว่าค่าที่ใช้โดยการทดสอบและสิ่งที่ใช้ใน strncpy เป็นสองค่าที่ต่างกัน อย่างไรก็ตามแบบฝึกหัดการเข้ารหัสทั่วไปบอกว่าขีด จำกัด การคัดลอกควรเป็นขนาดของ (บัฟเฟอร์) - 1 ดังนั้นจึงไม่สำคัญว่าความยาวของ str จะอยู่บนสำเนา strncpy จะหยุดการคัดลอกไบต์เมื่อมันกระทบ null หรือคัดลอก n ไบต์ บรรทัดถัดไปรับรองได้ว่าไบต์สุดท้ายในบัฟเฟอร์เป็นอักขระถ่าน รหัสมีความปลอดภัยฉันยืนตามคำสั่งก่อนหน้าของฉัน
Daniel Rudy

11

แม้ว่าคุณจะใช้strncpyความยาวของการตัดยอดจะขึ้นอยู่กับตัวชี้สตริงที่ส่งผ่าน คุณไม่รู้เลยว่าสตริงนั้นยาวแค่ไหน (ตำแหน่งของตัวสิ้นสุดเทอร์มินัลที่สัมพันธ์กับตัวชี้นั่นคือ) ดังนั้นการโทรstrlenเพียงอย่างเดียวจะเปิดช่องโหว่ให้คุณ หากคุณต้องการที่จะเป็นความปลอดภัยมากขึ้น, strnlen(str, 100)การใช้งาน

แก้ไขรหัสเต็มจะเป็น:

int func(char *str) {
   char buffer[100];
   unsigned short len = strnlen(str, 100); // sizeof buffer

   if (len >= 100) {
     return -1;
   }

   strcpy(buffer, str); // this is safe since null terminator is less than 100th index
   return 0;
}

@ user3386109 จะไม่strlenสามารถเข้าถึงผ่านจุดสิ้นสุดของบัฟเฟอร์ได้หรือไม่
Patrick Roberts

2
@ user3386109 สิ่งที่คุณชี้ให้เห็นทำให้คำตอบของ orlp ไม่ถูกต้องเหมือนกับของฉัน ฉันล้มเหลวที่จะดูว่าทำไมstrnlenไม่แก้ปัญหาถ้าสิ่งที่ orlp แนะนำคือถูกต้องควรจะอยู่แล้ว
Patrick Roberts

1
"ฉันไม่คิดว่า strnlen แก้อะไรที่นี่" - แน่นอนว่ามัน; bufferจะป้องกันไม่ให้ล้น "เนื่องจาก str สามารถชี้ไปที่บัฟเฟอร์ 2 ไบต์ทั้งที่ไม่ใช่ NUL" - ที่ไม่เกี่ยวข้องตามที่มันเป็นจริงของการใด ๆfuncการดำเนินงานของ คำถามที่นี่เป็นเรื่องเกี่ยวกับบัฟเฟอร์ล้นไม่ใช่ UB เพราะอินพุตไม่ได้สิ้นสุดด้วย NUL
Jim Balter

1
"พารามิเตอร์ที่สองที่ส่งไปยัง strnlen ต้องมีขนาดของวัตถุที่พารามิเตอร์แรกชี้ไปหรือ strnlen ไม่มีค่า" - นี่เสร็จสมบูรณ์และไร้สาระที่สุด หากอาร์กิวเมนต์ที่สองของ strnlen คือความยาวของสตริงอินพุตดังนั้น strnlen จะเทียบเท่ากับ strlen คุณจะได้รับหมายเลขนั้นอย่างไรและถ้าคุณมีมันทำไมคุณต้องโทรหา [n] len? นั่นไม่ใช่สิ่งที่ strnlen มีไว้สำหรับทุกคน
Jim Balter

1
+1 แม้ว่าคำตอบนี้ไม่สมบูรณ์เพราะมันไม่เทียบเท่ากับรหัสของ OP - strncpy NUL-pads และ NUL ไม่สิ้นสุดในขณะที่ strcpy NUL ยุติและไม่ได้ NUL-pad มันจะแก้ปัญหาตรงกันข้ามกับ ความคิดเห็นที่ไร้สาระไร้สาระไร้สาระ
Jim Balter

4

คำตอบที่ถูกต้องถูกต้อง แต่มีปัญหาฉันคิดว่าไม่ได้กล่าวถึงถ้า (len> = 100)

ถ้าเลนจะเท่ากับ 100 เราจะคัดลอก 100 องค์ประกอบแล้วเราจะไม่ได้ตามท้าย \ 0 ชัดเจนว่าจะหมายถึงฟังก์ชั่นอื่น ๆ ขึ้นอยู่กับสตริงสิ้นสุดที่เหมาะสมจะเดินเกินกว่าอาร์เรย์เดิม

สตริงที่เป็นปัญหาจาก C นั้นไม่สามารถกู้คืนได้ IMHO คุณควรมีข้อ จำกัด ก่อนการโทรเสมอ แต่ถึงแม้จะไม่ช่วยก็ตาม ไม่มีการตรวจสอบขอบเขตดังนั้นบัฟเฟอร์โอเวอร์โฟลว์สามารถทำได้และน่าเสียดายที่จะเกิดขึ้น ....


สตริงที่เป็นปัญหาสามารถแก้ไขได้: เพียงแค่ใช้ฟังก์ชั่นที่เหมาะสม I. e. ไม่ใช่ strncpy()และเพื่อน ๆ แต่หน่วยความจำที่จัดสรรหน้าที่เช่นstrdup()และเพื่อน ๆ พวกเขาอยู่ในมาตรฐาน POSIX-2008 ดังนั้นจึงพกพาได้อย่างเป็นธรรมแม้ว่าจะไม่สามารถใช้ได้กับระบบที่เป็นกรรมสิทธิ์บางระบบ
cmaster - คืนสถานะโมนิก้า

"ฟังก์ชั่นอื่น ๆ ขึ้นอยู่กับสตริงที่เหมาะสม" - bufferเป็นฟังก์ชั่นเฉพาะที่นี้และไม่ได้ใช้ที่อื่น ในโปรแกรมจริงเราจะต้องตรวจสอบว่ามันถูกใช้อย่างไร ... บางครั้งไม่สามารถยกเลิก NUL ได้อย่างถูกต้อง (การใช้ strncpy ดั้งเดิมคือการสร้างรายการไดเรกทอรี 14 ไบต์ของ UNIX - NUL-padded และไม่สิ้นสุดด้วย NUL) "ปัญหาสตริงจาก C คือ IMHO แก้ไม่ได้" - ในขณะที่ C เป็นภาษาที่น่าเกรงขามซึ่งถูกค้นพบด้วยเทคโนโลยีที่ดีกว่าไกลกว่านั้นสามารถเขียนรหัสที่ปลอดภัยได้หากมีการใช้วินัยอย่างเพียงพอ
Jim Balter

การสังเกตของคุณดูเหมือนว่าฉันเข้าใจผิด if (len >= 100)เป็นเงื่อนไขสำหรับเมื่อการตรวจสอบล้มเหลวไม่ใช่เมื่อผ่านซึ่งหมายความว่าไม่มีกรณีที่คัดลอก 100 ไบต์ที่ไม่มีตัวคั่น NUL ทั้งหมดเนื่องจากความยาวนั้นรวมอยู่ในเงื่อนไขความล้มเหลว
Patrick Roberts

@ cmaster ในกรณีนี้คุณผิด มันไม่สามารถแก้ไขได้เพราะใคร ๆ ก็สามารถเขียน beyoned ขอบเขต ใช่มันเป็นพฤติกรรมที่ไม่ได้นิยาม แต่ก็ไม่มีวิธีที่จะป้องกันได้อย่างสมบูรณ์
ฟรีดริช

@Jim Balter ไม่เป็นไร. ฉันอาจเขียนทับขอบเขตของบัฟเฟอร์ในเครื่องนี้และดังนั้นจึงเป็นไปได้ที่จะทำลายดาต้าเบสโครงสร้างอื่น ๆ
ฟรีดริช

3

นอกเหนือจากปัญหาด้านความปลอดภัยที่เกี่ยวข้องกับการโทรstrlenมากกว่าหนึ่งครั้งโดยทั่วไปแล้วไม่ควรใช้วิธีสตริงในสตริงที่มีความยาวเป็นที่รู้จักอย่างแม่นยำ [สำหรับฟังก์ชั่นสตริงส่วนใหญ่มีเพียงกรณีแคบจริง ๆ ที่ควรใช้ - บนสตริงที่มีค่าสูงสุด สามารถรับประกันความยาวได้ แต่ไม่ทราบความยาวที่แน่นอน] เมื่อทราบความยาวของสตริงอินพุตและทราบความยาวของบัฟเฟอร์เอาต์พุตแล้วควรทราบว่าควรคัดลอกพื้นที่ขนาดใหญ่เท่าใดจากนั้นใช้memcpy()เพื่อดำเนินการคัดลอกที่เป็นปัญหา แม้ว่าอาจเป็นไปได้ที่strcpyอาจมีประสิทธิภาพสูงกว่าmemcpy()เมื่อคัดลอกสตริงเพียง 1-3 ไบต์หรือมากกว่านั้น แต่ในหลาย ๆ แพลตฟอร์มmemcpy()มีแนวโน้มว่าจะเร็วกว่าสองเท่าเมื่อจัดการกับสตริงที่มีขนาดใหญ่กว่า

แม้ว่าจะมีบางสถานการณ์ที่การรักษาความปลอดภัยจะมาที่ค่าใช้จ่ายของการทำงานนี้เป็นสถานการณ์ที่วิธีการรักษาความปลอดภัยเป็นยังเร็วขึ้นหนึ่ง ในบางกรณีอาจมีเหตุผลที่จะเขียนโค้ดที่ไม่ปลอดภัยกับอินพุตที่มีพฤติกรรมแปลก ๆ หากโค้ดที่ป้อนอินพุตสามารถมั่นใจได้ว่าพวกเขาจะทำงานได้ดีและหากการปกป้องอินพุตที่ไม่ดี เพื่อให้มั่นใจว่าเชือกยาวมีการตรวจสอบเพียงครั้งเดียวช่วยเพิ่มทั้งประสิทธิภาพและความปลอดภัย แต่สิ่งที่พิเศษอย่างใดอย่างหนึ่งที่สามารถทำได้เพื่อช่วยเหลือพนักงานรักษาความปลอดภัยแม้ในขณะที่การติดตามความยาวสายด้วยตนเอง: สตริงซึ่งคาดว่าจะมี null ต่อท้ายทุกเขียน null ต่อท้ายอย่างชัดเจนมากกว่า กว่าที่คาดหวังว่าจะมีซอร์สสตริง ดังนั้นหากมีคนเขียนstrdupเทียบเท่า:

char *strdupe(char const *src)
{
  size_t len = strlen(src);
  char *dest = malloc(len+1);
  // Calculation can't wrap if string is in valid-size memory block
  if (!dest) return (OUT_OF_MEMORY(),(char*)0); 
  // OUT_OF_MEMORY is expected to halt; the return guards if it doesn't
  memcpy(dest, src, len);      
  dest[len]=0;
  return dest;
}

โปรดสังเกตว่าโดยทั่วไปคำสั่งสุดท้ายจะถูกละเว้นหาก memcpy ประมวลผลเป็นlen+1ไบต์ แต่เป็นเธรดอื่นเพื่อแก้ไขสตริงต้นทางผลลัพธ์อาจเป็นสตริงปลายทางที่ไม่สิ้นสุด NUL


3
คุณช่วยอธิบายเกี่ยวกับปัญหาด้านความปลอดภัยที่เกี่ยวข้องกับการโทรได้strlenมากกว่าหนึ่งครั้งได้ไหม
Bogdan Alexandru

1
@BogdanAlexandru: เมื่อมีการเรียกstrlenและดำเนินการตามค่าที่ส่งคืน (ซึ่งน่าจะเป็นเหตุผลในการโทรหาในตอนแรก) จากนั้นการเรียกซ้ำ (1) จะให้คำตอบเดียวกับคำตอบแรกเสมอ ในกรณีที่มันสูญเปล่างานหรือ (2) อาจบางครั้ง (เพราะอย่างอื่น - บางทีกระทู้อื่น - แก้ไขสตริงในระหว่างนี้) ให้คำตอบที่แตกต่างกันซึ่งรหัสกรณีที่ทำบางสิ่งที่มีความยาว (เช่น การจัดสรรบัฟเฟอร์) อาจถือว่าขนาดแตกต่างจากรหัสที่ทำสิ่งอื่น ๆ (การคัดลอกไปยังบัฟเฟอร์)
supercat
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.