นี่เป็นการทบทวนครั้งที่สองในชุดบทความเกี่ยวกับการทดสอบโปรแกรมโอเพ่นซอร์สสำหรับการทำงานกับโปรโตคอล RDP ในนั้นเราจะดูที่ไคลเอนต์ rdesktop และเซิร์ฟเวอร์ xrdp
ใช้เป็นเครื่องมือในการระบุข้อผิดพลาด
บทความนี้นำเสนอเฉพาะข้อผิดพลาดที่ดูน่าสนใจสำหรับฉันเท่านั้น อย่างไรก็ตาม โครงการมีขนาดเล็ก จึงมีข้อผิดพลาดเล็กน้อย :)
หมายเหตุ. สามารถดูบทความก่อนหน้าเกี่ยวกับการตรวจสอบโครงการ FreeRDP ได้
เดสก์ท็อป
ไคลเอนต์นี้ได้รับความนิยมอย่างมาก - มันถูกใช้เป็นค่าเริ่มต้นใน ReactOS และคุณยังสามารถค้นหาส่วนหน้าแบบกราฟิกของบุคคลที่สามได้อีกด้วย อย่างไรก็ตามเขาอายุค่อนข้างมาก: การเปิดตัวครั้งแรกเกิดขึ้นเมื่อวันที่ 4 เมษายน พ.ศ. 2001 ในขณะที่เขียนเขาอายุ 17 ปี
อย่างที่บอกไปแล้วว่าโปรเจ็กต์นี้ค่อนข้างเล็ก มีโค้ดประมาณ 30 บรรทัด ซึ่งค่อนข้างแปลกเมื่อพิจารณาจากอายุของมัน สำหรับการเปรียบเทียบ FreeRDP มี 320 บรรทัด นี่คือผลลัพธ์ของโปรแกรม Cloc:
รหัสที่ไม่สามารถเข้าถึงได้
int
main(int argc, char *argv[])
{
....
return handle_disconnect_reason(deactivated, ext_disc_reason);
if (g_redirect_username)
xfree(g_redirect_username);
xfree(g_username);
}
ข้อผิดพลาดพบเราทันทีในฟังก์ชัน หลัก: เราเห็นโค้ดมาตามหลังโอเปอเรเตอร์ กลับ — ส่วนนี้ดำเนินการทำความสะอาดหน่วยความจำ อย่างไรก็ตามข้อผิดพลาดไม่ก่อให้เกิดภัยคุกคาม: หน่วยความจำที่จัดสรรทั้งหมดจะถูกล้างโดยระบบปฏิบัติการหลังจากออกจากโปรแกรม
ไม่มีการจัดการข้อผิดพลาด
RD_BOOL
subprocess(char *const argv[], str_handle_lines_t linehandler, void *data)
{
int n = 1;
char output[256];
....
while (n > 0)
{
n = read(fd[0], output, 255);
output[n] = ' '; // <=
str_handle_lines(output, &rest, linehandler, data);
}
....
}
ข้อมูลโค้ดในกรณีนี้จะอ่านจากไฟล์ไปยังบัฟเฟอร์จนกว่าไฟล์จะสิ้นสุด อย่างไรก็ตาม ไม่มีการจัดการข้อผิดพลาดที่นี่ หากมีสิ่งผิดปกติเกิดขึ้น อ่าน จะส่งกลับ -1 จากนั้นอาร์เรย์จะถูกโอเวอร์รัน เอาท์พุต.
การใช้ EOF ในประเภทถ่าน
int
ctrl_send_command(const char *cmd, const char *arg)
{
char result[CTRL_RESULT_SIZE], c, *escaped;
....
while ((c = fgetc(fp)) != EOF && index < CTRL_RESULT_SIZE && c != 'n')
{
result[index] = c;
index++;
}
....
}
ที่นี่เราเห็นการจัดการที่ไม่ถูกต้องในการไปถึงจุดสิ้นสุดของไฟล์: ถ้า fgetc ส่งคืนอักขระที่มีรหัสเป็น 0xFF ซึ่งจะถูกตีความเป็นจุดสิ้นสุดของไฟล์ (EOF).
EOF มันเป็นค่าคงที่ โดยปกติกำหนดให้เป็น -1 ตัวอย่างเช่นในการเข้ารหัส CP1251 ตัวอักษรตัวสุดท้ายของตัวอักษรรัสเซียจะมีรหัส 0xFF ซึ่งสอดคล้องกับตัวเลข -1 หากเรากำลังพูดถึงตัวแปรเช่น ถัง. ปรากฎว่าสัญลักษณ์ 0xFF เช่น EOF (-1) ถูกตีความเป็นจุดสิ้นสุดของไฟล์ เพื่อหลีกเลี่ยงข้อผิดพลาดดังกล่าว ผลลัพธ์ของฟังก์ชันคือ fgetc ควรเก็บไว้ในตัวแปรเช่น int.
ความผิดพลาด
ส่วนที่ 1
RD_NTSTATUS
disk_set_information(....)
{
time_t write_time, change_time, access_time, mod_time;
....
if (write_time || change_time)
mod_time = MIN(write_time, change_time);
else
mod_time = write_time ? write_time : change_time; // <=
....
}
บางทีผู้เขียนรหัสนี้อาจผิด || и && อยู่ในสภาพ ลองพิจารณาตัวเลือกที่เป็นไปได้สำหรับค่าต่างๆ write_time и เปลี่ยน_เวลา:
- ตัวแปรทั้งสองมีค่าเท่ากับ 0: ในกรณีนี้เราจะไปสิ้นสุดที่สาขา อื่น: ตัวแปร mod_time จะเป็น 0 เสมอโดยไม่คำนึงถึงเงื่อนไขที่ตามมา
- หนึ่งในตัวแปรคือ 0: mod_time จะเท่ากับ 0 (โดยมีเงื่อนไขว่าตัวแปรอื่นมีค่าไม่เป็นลบ) เพราะ นาที จะเลือกตัวเลือกที่เล็กกว่าจากทั้งสองตัวเลือก
- ตัวแปรทั้งสองไม่เท่ากับ 0: เลือกค่าต่ำสุด
เมื่อเปลี่ยนเงื่อนไขด้วย write_time && change_time พฤติกรรมจะดูถูกต้อง:
- ตัวแปรหนึ่งหรือทั้งสองไม่เท่ากับ 0: เลือกค่าที่ไม่ใช่ศูนย์
- ตัวแปรทั้งสองไม่เท่ากับ 0: เลือกค่าต่ำสุด
ส่วนที่ 2
static RD_NTSTATUS
disk_device_control(RD_NTHANDLE handle, uint32 request, STREAM in,
STREAM out)
{
....
if (((request >> 16) != 20) || ((request >> 16) != 9))
return RD_STATUS_INVALID_PARAMETER;
....
}
เห็นได้ชัดว่าผู้ดำเนินการก็ผสมปนเปกันที่นี่เช่นกัน || и &&หรือ == и !=: ตัวแปรไม่สามารถมีค่า 20 และ 9 ในเวลาเดียวกันได้
คัดลอกบรรทัดได้ไม่จำกัด
RD_NTSTATUS
disk_query_directory(....)
{
....
char *dirname, fullpath[PATH_MAX];
....
/* Get information for directory entry */
sprintf(fullpath, "%s/%s", dirname, pdirent->d_name);
....
}
เมื่อดูฟังก์ชันทั้งหมดจะพบว่าโค้ดนี้ไม่ทำให้เกิดปัญหาแต่อย่างใด อย่างไรก็ตาม สิ่งเหล่านี้อาจเกิดขึ้นได้ในอนาคต: การเปลี่ยนแปลงที่ไม่ระมัดระวังเพียงครั้งเดียวและเราจะได้รับบัฟเฟอร์ล้น - วิ่ง ไม่ถูกจำกัดด้วยสิ่งใดๆ ดังนั้นเมื่อเชื่อมเส้นทางเข้าด้วยกัน เราสามารถก้าวข้ามขอบเขตของอาร์เรย์ได้ ขอแนะนำให้สังเกตการโทรนี้ snprintf (เส้นทางแบบเต็ม, PATH_MAX, ….).
สภาพที่ซ้ำซ้อน
static void
inRepos(STREAM in, unsigned int read)
{
SERVER_DWORD add = 4 - read % 4;
if (add < 4 && add > 0)
{
....
}
}
ตรวจสอบ เพิ่ม > 0 ไม่จำเป็นที่นี่: ตัวแปรจะมากกว่าศูนย์เสมอเพราะว่า อ่าน% 4 จะส่งกลับส่วนที่เหลือของการหาร แต่จะไม่เท่ากับ 4
xrdp
- xrdp - การใช้โปรโตคอล เผยแพร่ภายใต้ลิขสิทธิ์ Apache 2.0
- xorgxrdp - ชุดไดรเวอร์ Xorg สำหรับใช้กับ xrdp ใบอนุญาต - X11 (เช่น MIT แต่ห้ามใช้ในการโฆษณา)
การพัฒนาโครงการขึ้นอยู่กับผลลัพธ์ของ rdesktop และ FreeRDP เริ่มแรกในการทำงานกับกราฟิกคุณต้องใช้เซิร์ฟเวอร์ VNC แยกต่างหากหรือเซิร์ฟเวอร์ X11 พิเศษที่รองรับ RDP - X11rdp แต่ด้วยการถือกำเนิดของ xorgxrdp ความต้องการจึงหายไป
ในบทความนี้ เราจะไม่กล่าวถึง xorgxrdp
โปรเจ็กต์ xrdp เช่นเดียวกับโปรเจ็กต์ก่อนหน้า มีขนาดเล็กมากและมีประมาณ 80 บรรทัด
พิมพ์ผิดมากขึ้น
static int
rfx_encode_format_rgb(const char *rgb_data, int width, int height,
int stride_bytes, int pixel_format,
uint8 *r_buf, uint8 *g_buf, uint8 *b_buf)
{
....
switch (pixel_format)
{
case RFX_FORMAT_BGRA:
....
while (x < 64)
{
*lr_buf++ = r;
*lg_buf++ = g;
*lb_buf++ = r; // <=
x++;
}
....
}
....
}
รหัสนี้นำมาจากไลบรารี librfxcodec ซึ่งใช้ตัวแปลงสัญญาณ jpeg2000 สำหรับ RemoteFX เห็นได้ชัดว่าช่องข้อมูลกราฟิกผสมกัน - แทนที่จะเป็นสี "สีน้ำเงิน" บันทึก "สีแดง" ข้อผิดพลาดนี้น่าจะเกิดจากการคัดลอกและวาง
ปัญหาเดียวกันเกิดขึ้นในฟังก์ชันที่คล้ายกัน rfx_encode_format_argbซึ่งผู้วิเคราะห์บอกเราด้วยว่า:
while (x < 64)
{
*la_buf++ = a;
*lr_buf++ = r;
*lg_buf++ = g;
*lb_buf++ = r;
x++;
}
การประกาศอาร์เรย์
// evdev-map.c
int xfree86_to_evdev[137-8+1] = {
....
};
// genkeymap.c
extern int xfree86_to_evdev[137-8];
int main(int argc, char **argv)
{
....
for (i = 8; i <= 137; i++) /* Keycodes */
{
if (is_evdev)
e.keycode = xfree86_to_evdev[i-8];
....
}
....
}
การประกาศและคำจำกัดความของอาร์เรย์ในไฟล์ทั้งสองนี้เข้ากันไม่ได้ - ขนาดแตกต่างกัน 1 อย่างไรก็ตาม ไม่มีข้อผิดพลาดเกิดขึ้น - ขนาดที่ถูกต้องระบุไว้ในไฟล์ evdev-map.c ดังนั้นจึงไม่มีขอบเขต นี่เป็นเพียงข้อบกพร่องที่สามารถแก้ไขได้ง่าย
การเปรียบเทียบที่ไม่ถูกต้อง
// common/parse.h
#if defined(B_ENDIAN) || defined(NEED_ALIGN)
#define in_uint16_le(s, v) do
....
#else
#define in_uint16_le(s, v) do
{
(v) = *((unsigned short*)((s)->p));
(s)->p += 2;
} while (0)
#endif
int
xrdp_caps_process_confirm_active(struct xrdp_rdp *self, struct stream *s)
{
int cap_len;
....
in_uint16_le(s, cap_len);
....
if ((cap_len < 0) || (cap_len > 1024 * 1024))
{
....
}
....
}
ฟังก์ชันอ่านตัวแปรประเภท สั้นไม่ได้ลงนาม ให้เป็นตัวแปรเช่น int. ไม่จำเป็นต้องตรวจสอบที่นี่เนื่องจากเรากำลังอ่านตัวแปรที่ไม่ได้ลงชื่อและกำหนดผลลัพธ์ให้กับตัวแปรที่ใหญ่กว่า ดังนั้นตัวแปรจึงไม่สามารถรับค่าลบได้
การตรวจสอบที่ไม่จำเป็น
int EXPORT_CC
libxrdp_send_pointer(struct xrdp_session *session, int cache_idx,
char *data, char *mask, int x, int y, int bpp)
{
....
if ((bpp == 15) && (bpp != 16) && (bpp != 24) && (bpp != 32))
{
g_writeln("libxrdp_send_pointer: error");
return 1;
}
....
}
การตรวจสอบความไม่เท่าเทียมกันไม่สมเหตุสมผลที่นี่เนื่องจากเรามีการเปรียบเทียบอยู่แล้วตั้งแต่ต้น มีแนวโน้มว่านี่เป็นการพิมพ์ผิดและนักพัฒนาซอฟต์แวร์ต้องการใช้โอเปอเรเตอร์ || เพื่อกรองข้อโต้แย้งที่ไม่ถูกต้องออกไป
ข้อสรุป
ในระหว่างการตรวจสอบ ไม่พบข้อผิดพลาดร้ายแรง แต่พบข้อบกพร่องหลายประการ อย่างไรก็ตาม การออกแบบเหล่านี้ถูกใช้ในหลายระบบ แม้ว่าจะมีขอบเขตน้อยก็ตาม โปรเจ็กต์ขนาดเล็กไม่จำเป็นต้องมีข้อผิดพลาดมากมาย ดังนั้นคุณจึงไม่ควรตัดสินประสิทธิภาพของเครื่องวิเคราะห์เฉพาะโปรเจ็กต์ขนาดเล็กเท่านั้น คุณสามารถอ่านเพิ่มเติมเกี่ยวกับเรื่องนี้ได้ในบทความ “
คุณสามารถดาวน์โหลด PVS-Studio เวอร์ชันทดลองได้จากเรา
หากคุณต้องการแบ่งปันบทความนี้กับผู้ชมที่พูดภาษาอังกฤษ โปรดใช้ลิงก์การแปล: Sergey Larin
ที่มา: will.com