این دومین بررسی از سری مقالات در مورد آزمایش برنامه های منبع باز برای کار با پروتکل 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 در نوع char
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++;
}
....
}
در اینجا شاهد مدیریت نادرست رسیدن به انتهای فایل هستیم: if 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; // <=
....
}
شاید نویسنده این کد اشتباه کرده است || и && در شرایط بیایید گزینه های ممکن برای مقادیر را در نظر بگیریم نوشتن_زمان и تغییر_زمان:
- هر دو متغیر برابر با 0 هستند: در این حالت ما در یک شاخه قرار می گیریم دیگر: متغیر mod_time بدون توجه به شرایط بعدی همیشه 0 خواهد بود.
- یکی از متغیرها 0 است: mod_time برابر 0 خواهد بود (به شرطی که متغیر دیگر مقدار غیر منفی داشته باشد)، زیرا MIN از بین دو گزینه کوچکتر را انتخاب می کند.
- هر دو متغیر برابر با 0 نیستند: حداقل مقدار را انتخاب کنید.
هنگام جایگزینی شرایط با writ_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 - پیاده سازی پروتکل. تحت مجوز آپاچی 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.
منبع: www.habr.com