
Dette er den anden anmeldelse i rÊkken af artikler om test af Äbne programmer til RDP-protokollen. Her ser vi pÄ klienten rdesktop og serveren xrdp.
Til fejlanalyse blev der anvendt . Dette er en statisk kodeanalysator til C-, C++-, C#- og Java-sprog, tilgÊngelig pÄ platforme som Windows, Linux og macOS.
Artiklen prÊsenterer kun de fejl, der syntes interessante for mig. Men projekterne er smÄ, sÄ der var ikke mange fejl. :)
BemĂŠrk. Den tidligere artikel om test af FreeRDP-projektet kan findes .
rdesktop
â en fri implementering af RDP-klienten til UNIX-baserede systemer. Den kan ogsĂ„ bruges pĂ„ Windows, hvis projektet samles under Cygwin. Licenseret under GPLv3.
Denne klient har stor popularitet â den bruges som standard i ReactOS, og der findes ogsĂ„ tredjeparts grafiske front-end'er til den. Ikke desto mindre er den temmelig gammel: den fĂžrste udgivelse fandt sted den 4. april 2001 â pĂ„ tidspunktet for denne artikel er den 17 Ă„r gammel.
Som jeg nĂŠvnte tidligere, er projektet ganske lille. Det indeholder cirka 30.000 linjer kode, hvilket er lidt mĂŠrkeligt i betragtning af dets alder. Til sammenligning indeholder FreeRDP 320.000 linjer. Her er output fra programmet Cloc:

UopnÄelig kode
UopnÄelig kode registreret. Det er muligt, at der er en fejl til stede. 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);
}Fejlen rammer os straks i funktionen main: vi ser koden, der fĂžlger efter operatoren returner â dette fragment rydder op i hukommelsen. Ikke desto mindre udgĂžr fejlen ingen trussel: al tildelt hukommelse vil blive ryddet af operativsystemet efter programmets afslutning.
Manglende fejlhÄndtering
Array underrun er muligt. Indekset ânâ kunne nĂ„ -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);
}
....
}Kodefragmentet lÊser her fra en fil til en buffer, indtil filen er slut. Men fejlhÄndtering mangler her: hvis noget gÄr galt, sÄ write vil det returnere -1, og sÄ vil der ske et overtrÊdelse af array-grÊnsen output.
Brug af EOF i char-type
EOF skal ikke sammenlignes med en vĂŠrdi af 'char' typen. '(c = fgetc(fp))' bĂžr vĂŠre af 'int' typen. 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++;
}
....
}Her ser vi en ukorrekt hÄndtering af enden af filen: hvis fgetc returnerer et tegn, hvis kode er lig med 0xFF, vil det blive opfattet som enden af filen (EOF).
EOF det er en konstant, der normalt defineres som -1. For eksempel har den sidste bogstav i det russiske alfabet i kodning CP1251 koden 0xFF, hvilket svarer til tallet -1, hvis vi taler om en variabel af typen char. Det viser sig, at tegnet 0xFF, ligesom EOF (-1) opfattes som enden af filen. For at undgÄ sÄdanne fejl bÞr resultatet af funktionen fgetc opbevares i en variabel af typen int.
SlÄfejl
Fragment 1
Udtryk âwrite_timeâ er altid falsk. disk.c 805
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; <=
....
}MÄske forvekslede forfatteren af denne kode || og && i betingelsen. Lad os overveje mulige vÊrdier for write_time og change_time:
- Begge variabler er lig med 0: i dette tilfĂŠlde vil vi ende i grenen ellers: variabel mod_time vil altid vĂŠre lig med 0 uanset den efterfĂžlgende betingelse.
- En af variablerne er lig med 0: mod_time vil vĂŠre lig med 0 (forudsat at den anden variabel har en ikke-negativ vĂŠrdi), da MIN vĂŠlger den mindste af de to muligheder.
- Begge variabler er ikke lig med 0: vĂŠlg den mindste vĂŠrdi.
Ved at ĂŠndre betingelsen til write_time && change_time vil adfĂŠrden se korrekt ud:
- En eller begge variabler er ikke lig med 0: vĂŠlg en ikke-null vĂŠrdi.
- Begge variabler er ikke lig med 0: vĂŠlg den mindste vĂŠrdi.
Fragment 2
Udtryk er altid sandt. Sandsynligvis skal â&&â operatoren bruges her. disk.c 1419
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;
....
}Det ser ud til, at operatorerne ogsÄ er forvekslet her: || og &&, eller == og !=variablen kan ikke samtidig have vÊrdien 20 og 9.
UbegrĂŠnset kopiering af streng
Et kald til âsprintfâ funktionen vil fĂžre til overfyldning af bufferen âfullpathâ. disk.c 1257
RD_NTSTATUS
disk_query_directory(....)
{
....
char *dirname, fullpath[PATH_MAX];
....
/* Hent oplysninger for katalogpost */
sprintf(fullpath, "%s/%s", dirname, pdirent->d_name);
....
}Ved at se pĂ„ funktionen helt vil det blive klart, at denne kode ikke forĂ„rsager problemer. Men de kan opstĂ„ i fremtiden: en uforsigtig ĂŠndring, og vi fĂ„r buffer-overfyldning â sprintf er ikke begrĂŠnset, sĂ„ ved sammenkĂŠdning af stier kan vi overskride grĂŠnserne for arrayet. Det anbefales at bemĂŠrke dette kald til snprintf(fullpath, PATH_MAX, âŠ.).
Overskydende betingelse
En del af den betingede udtryk er altid sandt: tilfĂžj > 0. scard.c 507
static void
inRepos(STREAM in, unsigned int read)
{
SERVER_DWORD add = 4 - read % 4;
if (add 0)
{
....
}
}Kontrol add > 0 her er ingen grund: variablen vil altid vĂŠre stĂžrre end nul, da read % 4 vil returnere resten af divisionen, og den vil aldrig vĂŠre lig med 4.
xrdp
â implementering af RDP-server med Ă„ben kildekode. Projektet er opdelt i 2 dele:
- xrdp â implementeringen af protokollen. Distribueres under Apache 2.0 licensen.
- xorgxrdp â et sĂŠt Xorg-drivere til brug med xrdp. Licens â X11 (som MIT, men forbyder brug i reklame)
Udviklingen af projektet er baseret pĂ„ resultaterne fra rdesktop og FreeRDP. Oprindeligt mĂ„tte man bruge en separat VNC-server eller en sĂŠrlig X11-server med RDP-understĂžttelse â X11rdp, men med fremkomsten af xorgxrdp er behovet for dem forsvundet.
I denne artikel vil vi ikke komme ind pÄ xorgxrdp.
Projektet xrdp, ligesom det foregÄende, er ret lille og indeholder cirka 80.000 linjer.

Flere tastefejl
Koden indeholder samlingen af lignende blokke. Tjek elementer ârâ, âgâ, ârâ i linjer 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++;
}
....
}
....
}Denne kode er hentet fra biblioteket librfxcodec, der implementerer en jpeg2000 codec til brug med RemoteFX. Her ser det ud til, at de grafiske datakanaler er blevet forvekslet â i stedet for 'blĂ„' farve gemmes 'rĂžd'. En sĂ„dan fejl opstod sandsynligvis som fĂžlge af copy-paste.
Det samme problem ramte ogsÄ en lignende funktion rfx_encode_format_argb, som analysatoren ogsÄ informerede os om:
Koden indeholder samlingen af lignende blokke. Tjek elementer âaâ, ârâ, âgâ, ârâ i linjer 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++;
}Deklaration af array
Array-overtrÊdelse er muligt. VÊrdien af 'i - 8' indekset kunne nÄ 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];
....
}
....
}Deklarationen og definitionen af arrayet i disse to filer er inkompatible â stĂžrrelsen adskiller sig med 1. Men der opstĂ„r ikke fejl â i filen evdev-map.c er den korrekte stĂžrrelse angivet, sĂ„ der er ingen grĂŠnseoverskridelse. SĂ„ dette er bare en detalje, som er let at rette.
Ukorrekt sammenligning
En del af den betingede udtryk er altid falsk: (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))
{
....
}
....
}I funktionen sker der lÊsning af en variabel af typen unsigned short til en variabel af typen intHer er ingen kontrol nÞdvendig, da vi lÊser den usignede variabel og tildeler resultatet til en stÞrre variabel, sÄ variablen kan ikke fÄ en negativ vÊrdi.
UnĂždvendige kontroller
En del af den betingede udtryk er altid sand: (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: fejltagning");
return 1;
}
....
}Kontroller for ulighed her giver ingen mening, da vi allerede har en sammenligning i begyndelsen. Det er sandsynligt, at dette er en fejltagning, og udvikleren Ăžnskede at bruge operatĂžren || for at filtrere ugyldige argumenter.
Konklusion
Under kontrollen blev der ikke fundet alvorlige fejl, men mange smÄfejl blev identificeret. Ikke desto mindre anvendes disse projekter i mange systemer, omend de er smÄ i omfang. I et lille projekt behÞver der ikke vÊre mange fejl, sÄ man skal ikke dÞmme analysatorens arbejde kun ud fra smÄ projekter. LÊs mere om dette i artiklen "«.
Du kan downloade en prÞveversion af PVS-Studio hos os pÄ .
Hvis du Ăžnsker at dele denne artikel med et engelsktalende publikum, bedes du bruge linket til oversĂŠttelsen: Sergey Larin.
Kilde: habr.com
