
Ky është shqyrtimi i dytë nga cikli i artikujve për kontrollimin e programeve të hapura për të punuar me protokollin RDP. Në të do të shqyrtojmë klientin rdesktop dhe serverin xrdp.
Si një mjet për identifikimin e gabimeve u përdor . Ky është një analizues statik i kodit për gjuhët C, C++, C# dhe Java, i disponueshëm në platformat Windows, Linux dhe macOS.
Në këtë artikull janë të paraqitura vetëm ato gabime që më dukeshin interesante. Megjithatë, projektet janë të vogla, prandaj edhe gabimet ishin të pakta : ).
Shënim. Artikulli i mëparshëm për shqyrtimin e projektit FreeRDP mund të gjendet .
rdesktop
â njĂ« implementim i lirĂ« i klientit RDP pĂ«r sistemet UNIX-based. Ai gjithashtu mund tĂ« pĂ«rdoret edhe nĂ«n Windows, nĂ«se projekti ndĂ«rtohet nĂ«n Cygwin. Licencuar nĂ«n GPLv3.
Ky klient ka popullaritet tĂ« madh â ai pĂ«rdoret si parazgjedhje nĂ« ReactOS, gjithashtu pĂ«r tĂ« mund tĂ« gjenden front-end tĂ« tretĂ«. MegjithatĂ«, ai Ă«shtĂ« mjaft i vjetĂ«r: lĂ«shimi i parĂ« ndodhi mĂ« 4 prill 2001 â nĂ« momentin e shkrimit tĂ« artikullit, mosha e tij Ă«shtĂ« 17 vjet.
Siç e kam përmendur më parë, projekti është shumë i vogël. Ai përmban rreth 30 mijë rreshta kodi, që është pak e çuditshme, duke e marrë parasysh moshën e tij. Për krahasim, FreeRDP përmban 320 mijë rreshta. Ja rezultati i programit Cloc:

Kodi i paarrijshëm
Kodi i paarrijshĂ«m Ă«shtĂ« zbuluar. ĂshtĂ« e mundur qĂ« tĂ« jetĂ« njĂ« gabim. rdesktop.c 1502
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);
}Gabimi na takon menjĂ«herĂ« nĂ« funksionin main: ne shohim kodin qĂ« vjen pas operatorit return â ky fragment kryen pastrimin e memories. MegjithatĂ«, gabimi nuk paraqet ndonjĂ« kĂ«rcĂ«nim: tĂ« gjitha memoriet e ndara do tĂ« pastrohen nga sistemi operativ pas pĂ«rfundimit tĂ« programit.
Mungesa e përpunimit të gabimeve
Nënçeshtja e array është e mundur. Vlera e indeksit 'n' mund të arrijë -1. rdesktop.c 1872
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);
}
....
}Fragmenti i kodit në këtë rast lexon nga skedari në buffer derisa skedari të përfundojë. Megjithatë, përpunimi i gabimeve mungon këtu: nëse ndodhi ndonjë gjë e gabuar, read do të kthejë -1, dhe atëherë do të ndodhë tejkalimi i kufijve të array output.
Përdorimi i EOF në llojin char
EOF nuk duhet të krahasohet me një vlerë të llojit 'char'. '(c = fgetc(fp))' duhet të jetë e llojit 'int'. ctrl.c 500
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++;
}
....
}Këtu shohim trajtimin e papërshtatshëm të arrijmë fundin e skedarit: nëse fgetc kthen një karakter, kodi i të cilit është 0xFF, ai do të perceptohet si fund skedari (EOF).
EOF kjo është një konstantë, e cila zakonisht përcaktohet si -1. Për shembull, në kodimin CP1251, shkronja e fundit e alfabetit rus ka kodin 0xFF, i cili korrespondon me numrin -1, nëse flasim për një variabël të tipit char. Kështu që, karakteri 0xFF, ashtu si EOF (-1) perceptohet si fund skedari. Për të shmangur gabime të tilla, rezultati i funksionit fgetc duhet të ruhet në një variabël të tipit int.
Gabimet tipografike
Framenti 1
Shprehja âwrite_timeâ gjithmonĂ« Ă«shtĂ« e pavĂ«rtetĂ«. disk.c 805
RD_NTSTATUS
disk_set_information(....)
{
time_t write_time, change_time, access_time, mod_time;
....
nëse (write_time || change_time)
mod_time = MIN(write_time, change_time);
përndryshe
mod_time = write_time ? write_time : change_time; \/\/ <=
....
}Ndoshta autori i këtij kodi ka ngatërruar || dhe && në kushtin. Le të shqyrtojmë mundësitë e vlerave write_time dhe change_time:
- Të dy variablat janë të barabartë me 0: në këtë rast ne do të shkojmë në degen else: variabla mod_time do të jetë gjithmonë e barabartë me 0 pavarësisht nga kushti i mëpasshëm.
- Një nga variablat është e barabartë me 0: mod_time do të jetë 0 (me kusht që variabla tjetër të ketë një vlerë të paarritshme), pasi MIN do të zgjedhë më të voglin nga dy mundësitë.
- Të dy variablat nuk janë të barabarta me 0: zgjidhni vlerën minimale.
Duke zëvendësuar kushtin me write_time && change_time sjellja do të duket e saktë:
- Një ose të dy variablat nuk janë të barabarta me 0: zgjidhni një vlerë të ndryshme nga 0.
- Të dy variablat nuk janë të barabarta me 0: zgjidhni vlerën minimale.
Framenti 2
Shprehja gjithmonĂ« Ă«shtĂ« e vĂ«rtetĂ«. Ndoshta operatori â&&â duhet tĂ« pĂ«rdoret kĂ«tu. disk.c 1419
static RD_NTSTATUS
disk_device_control(RD_NTHANDLE handle, uint32 request, STREAM in,
STREAM out)
{
....
nëse (((request >> 16) != 20) || ((request >> 16) != 9))
kthe RD_STATUS_INVALID_PARAMETER;
....
}Duket se këtu janë ngatërruar operatorët || dhe &&, ose == dhe !=: variabla nuk mund të marrë njëkohësisht vlerën 20 dhe 9.
Kopjimi i pakufizuar i vargut
NjĂ« thirrje e funksionit âsprintfâ do tĂ« çojĂ« nĂ« tejkalimin e tamponit âfullpathâ. disk.c 1257
RD_NTSTATUS
disk_query_directory(....)
{
....
char *dirname, fullpath[PATH_MAX];
....
/* Merr informacion për hyrjen e drejtorisë */
sprintf(fullpath, "%s/%s", dirname, pdirent->d_name);
....
}Duke shqyrtuar funksionin plotĂ«sisht, do tĂ« jetĂ« e qartĂ« se ky kod nuk shkakton probleme. MegjithatĂ«, ato mund tĂ« shfaqen nĂ« tĂ« ardhmen: njĂ« ndryshim i pakujdesshĂ«m, dhe ne do tĂ« kemi tejkalim tamponi â sprintf nuk Ă«shtĂ« i kufizuar, prandaj gjatĂ« bashkimit tĂ« rrugĂ«ve ne mund tĂ« dalim jashtĂ« kufijve tĂ« array-t. Rekomandohet tĂ« vĂ«zhgohet kjo thirrje nĂ« snprintf(fullpath, PATH_MAX, âŠ.).
Kusht i tepruar
Një pjesë e shprehjes kushtore është gjithmonë e vërtetë: shtoni > 0. scard.c 507
static void
inRepos(STREAM in, unsigned int read)
{
SERVER_DWORD add = 4 - read % 4;
if (add 0)
{
....
}
}Kontrolli add > 0 nuk është ashtu: variabli do të jetë gjithmonë më i madh se zero, sepse read % 4 do të kthejë mbetjen e ndarjes, dhe ajo kurrë nuk do të jetë e barabartë me 4.
xrdp
â implementimi i serverit RDP me burim tĂ« hapur. Projekti ndahet nĂ« 2 pjesĂ«:
- xrdp â implementimi i protokollit. Distribuohet nĂ«n licencĂ«n Apache 2.0.
- xorgxrdp â njĂ« grup motorista Xorg pĂ«r t'u pĂ«rdorur me xrdp. Licenca â X11 (si MIT, por ndalon pĂ«rdorimin nĂ« reklama)
Zhvillimi i projektit bazohet nĂ« rezultatet e rdesktop dhe FreeRDP. Fillimisht pĂ«r tĂ« punuar me grafikĂ«n ishte e nevojshme tĂ« pĂ«rdorej njĂ« server i veçantĂ« VNC, ose njĂ« server i veçantĂ« X11 me mbĂ«shtetje pĂ«r RDP â X11rdp, megjithatĂ« me daljen e xorgxrdp nevoja pĂ«r to ra.
Në këtë artikull ne nuk do të trajtojmë xorgxrdp.
Projekti xrdp, si edhe ai i mëparshmi, është krejt i vogël dhe përmban rreth 80 mijë rreshta.

Ende gabime shtypi
Kodi përmban koleksionin e blloqeve të ngjashme. Kontrolloni artikujt 'r', 'g', 'r' në rreshtat 87, 88, 89. rfxencode_rgb_to_yuv.c 87
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++;
}
....
}
....
}Ky kod Ă«shtĂ« marrĂ« nga biblioteka librfxcodec, e cila implementon kodekun jpeg2000 pĂ«r tĂ« punuar me RemoteFX. KĂ«tu, duket se janĂ« ngatĂ«rruar kanalet e tĂ« dhĂ«nave grafike â nĂ« vend tĂ« ngjyrĂ«s 'blu' po regjistrohet 'e kuqe'. NjĂ« gabim i tillĂ«, me sa duket, ka lindur nga kopjimi-ngjitja.
Kjo problematikë ka prekur edhe një funksion të ngjashëm rfx_encode_format_argb, për të cilën na informoi gjithashtu analizeri:
Kodi përmban koleksionin e blloqeve të ngjashme. Kontrolloni artikujt 'a', 'r', 'g', 'r' në rreshtat 260, 261, 262, 263. rfxencode_rgb_to_yuv.c 260
while (x < 64)
{
*la_buf++ = a;
*lr_buf++ = r;
*lg_buf++ = g;
*lb_buf++ = r;
x++;
}Deklarimi i matrixit
Kryesisht ka mundësi për tejkalim të array-t. Vlera e indekset 'i - 8' mund të arrijë 129. genkeymap.c 142
// 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];
....
}
....
}Deklarimi dhe pĂ«rcaktimi i array-it nĂ« kĂ«to dy skedarĂ« Ă«shtĂ« i papĂ«rshtatshĂ«m â madhĂ«sia ndryshon me 1. MegjithatĂ« nuk ndodhin gabime â nĂ« skedarin evdev-map.c Ă«shtĂ« caktuar madhĂ«sia e saktĂ«, kĂ«shtu qĂ« nuk ka kalime pĂ«rtej kufijve. Pra, kjo Ă«shtĂ« thjesht njĂ« gabim qĂ« Ă«shtĂ« lehtĂ«sisht i zgjidhshĂ«m.
Krahasimi i papërshtatshëm
Një pjesë e shprehjes kushtore gjithmonë është e pavërtetë: (cap_len < 0). xrdp_caps.c 616
// 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))
{
....
}
....
}Në funksion ndodhi leximi i variants tipit unsigned short në një variant të tipit intKëtu nuk është e nevojshme të bëni kontrollin, pasi ne lexojmë variablën e tipit të padëshiruar dhe i japim rezultatit një variabël më të madhe, ndaj variabla nuk mund të marrë një vlerë negative.
Kontrolla të panevojshme
Një pjesë e shprehjes kushtore është gjithmonë e vërtetë: (bpp != 16). libxrdp.c 704
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: gabim");
return 1;
}
....
}Kontrollat për mospërputhje këtu nuk kanë kuptim, pasi ne tashmë kemi një krahasim në fillim. E mundshme është që kjo të jetë një gabim shtypi dhe zhvilluesi donte të përdorte operatorin || për të filtruar argumentet e pasakta.
Përfundim
Gjatë verifikimit, nuk u gjetën gabime serioze, por u identifikuan shumë parregullsi. Megjithatë, këto projekte përdoren në shumë sisteme, pavarësisht se janë të vogla në përmasë. Në një projekt të vogël, nuk është e nevojshme të ketë shumë gabime, prandaj nuk duhet të gjykohet puna e analizuesit vetëm mbi projekte të vogla. Më shumë rreth kësaj mund të lexoni në artikullin "«.
Mund të shkarkoni versionin provues të PVS-Studio nga ne në .
Nëse dëshironi të ndani këtë artikull me një audiencë anglishtfolëse, ju lutem përdorni lidhjen në përkthimin: Sergey Larin.
Burimi: habr.com
