FreeRDP β ΠΎΡΠΊΡΡΡΠ°Ρ ΡΠ΅Π°Π»ΠΈΠ·Π°ΡΠΈΡ Remote Desktop Protocol (RDP), ΠΏΡΠΎΡΠΎΠΊΠΎΠ»Π°, ΡΠ΅Π°Π»ΠΈΠ·ΡΡΡΠ΅Π³ΠΎ ΡΠ΄Π°Π»Π΅Π½Π½ΠΎΠ΅ ΡΠΏΡΠ°Π²Π»Π΅Π½ΠΈΠ΅ ΠΊΠΎΠΌΠΏΡΡΡΠ΅ΡΠΎΠΌ, ΡΠ°Π·ΡΠ°Π±ΠΎΡΠ°Π½Π½ΠΎΠ³ΠΎ ΠΊΠΎΠΌΠΏΠ°Π½ΠΈΠ΅ΠΉ Microsoft. ΠΡΠΎΠ΅ΠΊΡ ΠΏΠΎΠ΄Π΄Π΅ΡΠΆΠΈΠ²Π°Π΅Ρ ΠΌΠ½ΠΎΠΆΠ΅ΡΡΠ²ΠΎ ΠΏΠ»Π°ΡΡΠΎΡΠΌ, ΡΡΠ΅Π΄ΠΈ ΠΊΠΎΡΠΎΡΡΡ
Windows, Linux, macOS ΠΈ Π΄Π°ΠΆΠ΅ iOS Ρ Android. ΠΡΠΎΡ ΠΏΡΠΎΠ΅ΠΊΡ Π²ΡΠ±ΡΠ°Π½ ΠΏΠ΅ΡΠ²ΡΠΌ Π² ΡΠ°ΠΌΠΊΠ°Ρ
ΡΠΈΠΊΠ»Π° ΡΡΠ°ΡΠ΅ΠΉ, ΠΏΠΎΡΠ²ΡΡΠ΅Π½Π½ΡΡ
ΠΏΡΠΎΠ²Π΅ΡΠΊΠ΅ RDP-ΠΊΠ»ΠΈΠ΅Π½ΡΠΎΠ² Ρ ΠΏΠΎΠΌΠΎΡΡΡ ΡΡΠ°ΡΠΈΡΠ΅ΡΠΊΠΎΠ³ΠΎ Π°Π½Π°Π»ΠΈΠ·Π°ΡΠΎΡΠ° PVS-Studio.
ΠΠ΅ΠΌΠ½ΠΎΠ³ΠΎ ΠΈΡΡΠΎΡΠΈΠΈ
ΠΡΠΎΠ΅ΠΊΡ
Π ΠΏΡΠΎΡΠ΅ΡΡΠ΅ ΡΠ΅Π°Π»ΠΈΠ·Π°ΡΠΈΠΈ ΠΏΡΠΎΡΠΎΠΊΠΎΠ»Π° ΡΡΠ°Π½ΠΎΠ²ΠΈΠ»ΠΎΡΡ ΡΠ»ΠΎΠΆΠ½Π΅Π΅ Π΄ΠΎΠ±Π°Π²Π»ΡΡΡ Π½ΠΎΠ²ΡΠΉ ΡΡΠ½ΠΊΡΠΈΠΎΠ½Π°Π» ΠΈΠ·-Π·Π° ΡΡΡΠ΅ΡΡΠ²ΠΎΠ²Π°Π²ΡΠ΅ΠΉ ΡΠΎΠ³Π΄Π° Π°ΡΡ ΠΈΡΠ΅ΠΊΡΡΡΡ ΠΏΡΠΎΠ΅ΠΊΡΠ°. ΠΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΡ Π² Π½Π΅ΠΉ ΠΏΠΎΡΠΎΠ΄ΠΈΠ»ΠΈ ΠΊΠΎΠ½ΡΠ»ΠΈΠΊΡ ΠΌΠ΅ΠΆΠ΄Ρ ΡΠ°Π·ΡΠ°Π±ΠΎΡΡΠΈΠΊΠ°ΠΌΠΈ, ΡΡΠΎ ΠΏΡΠΈΠ²Π΅Π»ΠΎ ΠΊ ΡΠΎΠ·Π΄Π°Π½ΠΈΡ ΡΠΎΡΠΊΠ° rdesktop β FreeRDP. ΠΠ°Π»ΡΠ½Π΅ΠΉΡΠ΅Π΅ ΡΠ°ΡΠΏΡΠΎΡΡΡΠ°Π½Π΅Π½ΠΈΠ΅ ΠΏΡΠΎΠ΄ΡΠΊΡΠ° Π±ΡΠ»ΠΎ ΠΎΠ³ΡΠ°Π½ΠΈΡΠ΅Π½ΠΎ Π»ΠΈΡΠ΅Π½Π·ΠΈΠ΅ΠΉ GPLv2, Π² ΡΠ΅Π·ΡΠ»ΡΡΠ°ΡΠ΅ ΡΠ΅Π³ΠΎ Π±ΡΠ»ΠΎ ΠΏΡΠΈΠ½ΡΡΠΎ ΡΠ΅ΡΠ΅Π½ΠΈΠ΅ ΠΎ ΡΠ΅Π»ΠΈΡΠ΅Π½Π·ΠΈΡΠΎΠ²Π°Π½ΠΈΠΈ Π½Π° Apache License v2. ΠΠ΄Π½Π°ΠΊΠΎ Π½Π΅ Π²ΡΠ΅ Π±ΡΠ»ΠΈ ΡΠΎΠ³Π»Π°ΡΠ½Ρ ΠΌΠ΅Π½ΡΡΡ Π»ΠΈΡΠ΅Π½Π·ΠΈΡ ΡΠ²ΠΎΠ΅Π³ΠΎ ΠΊΠΎΠ΄Π°, ΠΏΠΎΡΡΠΎΠΌΡ ΡΠ°Π·ΡΠ°Π±ΠΎΡΡΠΈΠΊΠΈ ΡΠ΅ΡΠΈΠ»ΠΈ ΠΏΠ΅ΡΠ΅ΠΏΠΈΡΠ°ΡΡ ΠΏΡΠΎΠ΅ΠΊΡ, Π² ΡΠ΅Π·ΡΠ»ΡΡΠ°ΡΠ΅ ΡΠ΅Π³ΠΎ ΠΌΡ ΠΈΠΌΠ΅Π΅ΠΌ ΡΠΎΠ²ΡΠ΅ΠΌΠ΅Π½Π½ΡΠΉ Π²ΠΈΠ΄ ΠΊΠΎΠ΄ΠΎΠ²ΠΎΠΉ Π±Π°Π·Ρ.
ΠΠΎΠ»Π΅Π΅ ΠΏΠΎΠ΄ΡΠΎΠ±Π½ΠΎ ΠΎΠ± ΠΈΡΡΠΎΡΠΈΠΈ ΠΏΡΠΎΠ΅ΠΊΡΠ° ΠΌΠΎΠΆΠ½ΠΎ ΠΏΡΠΎΡΠ΅ΡΡΡ Π² Π·Π°ΠΌΠ΅ΡΠΊΠ΅ ΠΎΡΠΈΡΠΈΠ°Π»ΡΠ½ΠΎΠ³ΠΎ Π±Π»ΠΎΠ³Π°: Β«The history of the FreeRDP projectΒ».
Π ΠΊΠ°ΡΠ΅ΡΡΠ²Π΅ ΠΈΠ½ΡΡΡΡΠΌΠ΅Π½ΡΠ° Π΄Π»Ρ Π²ΡΡΠ²Π»Π΅Π½ΠΈΡ ΠΎΡΠΈΠ±ΠΎΠΊ ΠΈ ΠΏΠΎΡΠ΅Π½ΡΠΈΠ°Π»ΡΠ½ΡΡ
ΡΡΠ·Π²ΠΈΠΌΠΎΡΡΠ΅ΠΉ Π² ΠΊΠΎΠ΄Π΅ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π»ΡΡ
Π ΡΡΠ°ΡΡΠ΅ ΠΏΡΠ΅Π΄ΡΡΠ°Π²Π»Π΅Π½Ρ Π»ΠΈΡΡ ΡΠ΅ ΠΎΡΠΈΠ±ΠΊΠΈ, ΠΊΠΎΡΠΎΡΡΠ΅ ΠΏΠΎΠΊΠ°Π·Π°Π»ΠΈΡΡ ΠΌΠ½Π΅ Π½Π°ΠΈΠ±ΠΎΠ»Π΅Π΅ ΠΈΠ½ΡΠ΅ΡΠ΅ΡΠ½ΡΠΌΠΈ.
Π£ΡΠ΅ΡΠΊΠ° ΠΏΠ°ΠΌΡΡΠΈ
DWORD GetCurrentDirectoryA(DWORD nBufferLength, LPSTR lpBuffer)
{
char* cwd;
....
cwd = getcwd(NULL, 0);
....
if (lpBuffer == NULL)
{
free(cwd);
return 0;
}
if ((length + 1) > nBufferLength)
{
free(cwd);
return (DWORD) (length + 1);
}
memcpy(lpBuffer, cwd, length + 1);
return length;
....
}
ΠΠ°Π½Π½ΡΠΉ ΡΡΠ°Π³ΠΌΠ΅Π½Ρ Π±ΡΠ» Π²Π·ΡΡ ΠΈΠ· ΠΏΠΎΠ΄ΡΠΈΡΡΠ΅ΠΌΡ winpr, ΡΠ΅Π°Π»ΠΈΠ·ΡΡΡΠ΅ΠΉ ΠΎΠ±Π΅ΡΡΠΊΡ WINAPI Π΄Π»Ρ Π½Π΅-Windows ΡΠΈΡΡΠ΅ΠΌ, Ρ.Π΅. ΡΡΠΎ Π»Π΅Π³ΠΊΠΈΠΉ Π°Π½Π°Π»ΠΎΠ³ Wine. ΠΠ΄Π΅ΡΡ ΠΌΠΎΠΆΠ½ΠΎ Π·Π°ΠΌΠ΅ΡΠΈΡΡ ΡΡΠ΅ΡΠΊΡ: ΠΏΠ°ΠΌΡΡΡ, Π²ΡΠ΄Π΅Π»Π΅Π½Π½Π°Ρ ΡΡΠ½ΠΊΡΠΈΠ΅ΠΉ getcwd, ΠΎΡΠ²ΠΎΠ±ΠΎΠΆΠ΄Π°Π΅ΡΡΡ ΡΠΎΠ»ΡΠΊΠΎ ΠΏΡΠΈ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠ΅ ΡΠΏΠ΅ΡΠΈΠ°Π»ΡΠ½ΡΡ ΡΠ»ΡΡΠ°Π΅Π². ΠΠ»Ρ ΡΡΡΡΠ°Π½Π΅Π½ΠΈΡ ΠΎΡΠΈΠ±ΠΊΠΈ Π½ΡΠΆΠ½ΠΎ Π΄ΠΎΠ±Π°Π²ΠΈΡΡ Π²ΡΠ·ΠΎΠ² free ΠΏΠΎΡΠ»Π΅ memcpy.
ΠΡΡ ΠΎΠ΄ Π·Π° Π³ΡΠ°Π½ΠΈΡΡ ΠΌΠ°ΡΡΠΈΠ²Π°
#define MAX_EVENT_HANDLERS 32
struct _wEventType
{
....
int EventHandlerCount;
pEventHandler EventHandlers[MAX_EVENT_HANDLERS];
};
int PubSub_Subscribe(wPubSub* pubSub, const char* EventName,
pEventHandler EventHandler)
{
....
if (event->EventHandlerCount <= MAX_EVENT_HANDLERS)
{
event->EventHandlers[event->EventHandlerCount] = EventHandler;
event->EventHandlerCount++;
}
....
}
Π ΡΡΠΎΠΌ ΠΏΡΠΈΠΌΠ΅ΡΠ΅ Π½ΠΎΠ²ΡΠΉ ΡΠ»Π΅ΠΌΠ΅Π½Ρ Π΄ΠΎΠ±Π°Π²Π»ΡΠ΅ΡΡΡ Π² ΡΠΏΠΈΡΠΎΠΊ, Π΄Π°ΠΆΠ΅ Π΅ΡΠ»ΠΈ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ² Π΄ΠΎΡΡΠΈΠ³Π»ΠΎ ΠΌΠ°ΠΊΡΠΈΠΌΠ°Π»ΡΠ½ΠΎΠ³ΠΎ. ΠΠ΄Π΅ΡΡ Π΄ΠΎΡΡΠ°ΡΠΎΡΠ½ΠΎ Π·Π°ΠΌΠ΅Π½ΠΈΡΡ ΠΎΠΏΠ΅ΡΠ°ΡΠΎΡ <= Π½Π° <, ΡΡΠΎΠ±Ρ Π½Π΅ Π²ΡΡ ΠΎΠ΄ΠΈΡΡ Π·Π° Π³ΡΠ°Π½ΠΈΡΡ ΠΌΠ°ΡΡΠΈΠ²Π°.
ΠΡΠ»Π° Π½Π°ΠΉΠ΄Π΅Π½Π° ΠΈ Π΄ΡΡΠ³Π°Ρ ΠΎΡΠΈΠ±ΠΊΠ° ΡΠ°ΠΊΠΎΠ³ΠΎ ΡΠΈΠΏΠ°:
- V557 Array overrun is possible. The value of ‘iBitmapFormat’ index could reach 8. orders.c 2623
ΠΠΏΠ΅ΡΠ°ΡΠΊΠΈ
Π€ΡΠ°Π³ΠΌΠ΅Π½Ρ 1
wMessagePipe* MessagePipe_New()
{
....
pipe->In = MessageQueue_New(NULL);
if (!pipe->In)
goto error_in;
pipe->Out = MessageQueue_New(NULL);
if (!pipe->In) // <=
goto error_out;
....
}
Π’ΡΡ ΠΌΡ Π²ΠΈΠ΄ΠΈΠΌ ΠΎΠ±ΡΡΠ½ΡΡ ΠΎΠΏΠ΅ΡΠ°ΡΠΊΡ: Π²ΠΎ Π²ΡΠΎΡΠΎΠΌ ΡΡΠ»ΠΎΠ²ΠΈΠΈ Π²ΡΠΏΠΎΠ»Π½ΡΠ΅ΡΡΡ ΠΏΡΠΎΠ²Π΅ΡΠΊΠ° ΡΠΎΠΉ ΠΆΠ΅ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΠΎΠΉ, ΡΡΠΎ ΠΈ Π² ΠΏΠ΅ΡΠ²ΠΎΠΌ. Π‘ΠΊΠΎΡΠ΅Π΅ Π²ΡΠ΅Π³ΠΎ, ΠΎΡΠΈΠ±ΠΊΠ° ΠΏΠΎΡΠ²ΠΈΠ»Π°ΡΡ Π² ΡΠ΅Π·ΡΠ»ΡΡΠ°ΡΠ΅ Π½Π΅ΡΠ΄Π°ΡΠ½ΠΎΠ³ΠΎ ΠΊΠΎΠΏΠΈΡΠΎΠ²Π°Π½ΠΈΡ ΠΊΠΎΠ΄Π°.
Π€ΡΠ°Π³ΠΌΠ΅Π½Ρ 2
typedef struct _TSG_PACKET_VERSIONCAPS
{
....
UINT16 majorVersion;
UINT16 minorVersion;
....
} TSG_PACKET_VERSIONCAPS, *PTSG_PACKET_VERSIONCAPS;
static BOOL TsProxyCreateTunnelReadResponse(....)
{
....
PTSG_PACKET_VERSIONCAPS versionCaps = NULL;
....
/* MajorVersion (2 bytes) */
Stream_Read_UINT16(pdu->s, versionCaps->majorVersion);
/* MinorVersion (2 bytes) */
Stream_Read_UINT16(pdu->s, versionCaps->majorVersion);
....
}
ΠΡΠ΅ ΠΎΠ΄Π½Π° ΠΎΠΏΠ΅ΡΠ°ΡΠΊΠ°: ΠΊΠΎΠΌΠΌΠ΅Π½ΡΠ°ΡΠΈΠΉ ΠΊ ΠΊΠΎΠ΄Ρ ΠΏΠΎΠ΄ΡΠ°Π·ΡΠΌΠ΅Π²Π°Π΅Ρ, ΡΡΠΎ ΠΈΠ· ΠΏΠΎΡΠΎΠΊΠ° Π΄ΠΎΠ»ΠΆΠ½Π° ΠΏΡΠΈΠΉΡΠΈ minorVersion, ΠΎΠ΄Π½Π°ΠΊΠΎ ΡΡΠΈΡΡΠ²Π°Π½ΠΈΠ΅ ΠΏΡΠΎΠΈΡΡ ΠΎΠ΄ΠΈΡ Π² ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΡ Ρ ΠΈΠΌΠ΅Π½Π΅ΠΌ majorVersion. Π’Π΅ΠΌ Π½Π΅ ΠΌΠ΅Π½Π΅Π΅, Ρ Π½Π΅ Π·Π½Π°ΠΊΠΎΠΌ Ρ ΠΏΡΠΎΡΠΎΠΊΠΎΠ»ΠΎΠΌ, ΡΠ°ΠΊ ΡΡΠΎ ΡΡΠΎ Π»ΠΈΡΡ ΠΏΡΠ΅Π΄ΠΏΠΎΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅.
Π€ΡΠ°Π³ΠΌΠ΅Π½Ρ 3
/**
Find first occurrence of a character in a string.
....
*/
TRIO_PUBLIC_STRING char *
trio_index
TRIO_ARGS2((string, character),
TRIO_CONST char *string,
int character)
{
assert(string);
return strchr(string, character);
}
/**
Find last occurrence of a character in a string.
....
*/
TRIO_PUBLIC_STRING char *
trio_index_last
TRIO_ARGS2((string, character),
TRIO_CONST char *string,
int character)
{
assert(string);
return strchr(string, character);
}
Π‘ΡΠ΄Ρ ΠΏΠΎ ΠΊΠΎΠΌΠΌΠ΅Π½ΡΠ°ΡΠΈΡ, ΡΡΠ½ΠΊΡΠΈΡ trio_index Π½Π°Ρ ΠΎΠ΄ΠΈΡ ΠΏΠ΅ΡΠ²ΠΎΠ΅ ΡΠΎΠ²ΠΏΠ°Π΄Π΅Π½ΠΈΠ΅ ΡΠΈΠΌΠ²ΠΎΠ»Π° Π² ΡΡΡΠΎΠΊΠ΅, ΠΊΠΎΠ³Π΄Π° trio_index_last β ΠΏΠΎΡΠ»Π΅Π΄Π½Π΅Π΅. ΠΠΎ ΡΠ΅Π»Π° ΡΡΠΈΡ ΡΡΠ½ΠΊΡΠΈΠΉ ΠΈΠ΄Π΅Π½ΡΠΈΡΠ½Ρ! Π‘ΠΊΠΎΡΠ΅Π΅ Π²ΡΠ΅Π³ΠΎ, ΡΡΠΎ ΠΎΠΏΠ΅ΡΠ°ΡΠΊΠ°, ΠΈ Π² ΡΡΠ½ΠΊΡΠΈΠΈ trio_index_last Π½ΡΠΆΠ½ΠΎ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ strrchr Π²ΠΌΠ΅ΡΡΠΎ strchr. Π’ΠΎΠ³Π΄Π° ΠΏΠΎΠ²Π΅Π΄Π΅Π½ΠΈΠ΅ Π±ΡΠ΄Π΅Ρ ΠΎΠΆΠΈΠ΄Π°Π΅ΠΌΡΠΌ.
Π€ΡΠ°Π³ΠΌΠ΅Π½Ρ 4
static BOOL nsc_encode_argb_to_aycocg(NSC_CONTEXT* context,
const BYTE* data,
UINT32 scanline)
{
....
if (!context || data || (scanline == 0))
return FALSE;
....
src = data + (context->height - 1 - y) * scanline;
....
}
ΠΠΎΡ ΠΎΠΆΠ΅, Π·Π΄Π΅ΡΡ ΡΠ»ΡΡΠ°ΠΉΠ½ΠΎ ΠΏΡΠΎΠΏΡΡΡΠΈΠ»ΠΈ ΠΎΠΏΠ΅ΡΠ°ΡΠΎΡ ΠΎΡΡΠΈΡΠ°Π½ΠΈΡ ! ΡΡΠ΄ΠΎΠΌ Ρ data. Π‘ΡΡΠ°Π½Π½ΠΎ, ΡΡΠΎ ΡΡΠΎ ΠΎΡΡΠ°Π»ΠΎΡΡ Π½Π΅Π·Π°ΠΌΠ΅ΡΠ΅Π½Π½ΡΠΌ.
Π€ΡΠ°Π³ΠΌΠ΅Π½Ρ 5
BOOL rdpei_write_4byte_unsigned(wStream* s, UINT32 value)
{
BYTE byte;
if (value <= 0x3F)
{
....
}
else if (value <= 0x3FFF)
{
....
}
else if (value <= 0x3FFFFF)
{
byte = (value >> 16) & 0x3F;
Stream_Write_UINT8(s, byte | 0x80);
byte = (value >> 8) & 0xFF;
Stream_Write_UINT8(s, byte);
byte = (value & 0xFF);
Stream_Write_UINT8(s, byte);
}
else if (value <= 0x3FFFFF)
{
byte = (value >> 24) & 0x3F;
Stream_Write_UINT8(s, byte | 0xC0);
byte = (value >> 16) & 0xFF;
Stream_Write_UINT8(s, byte);
byte = (value >> 8) & 0xFF;
Stream_Write_UINT8(s, byte);
byte = (value & 0xFF);
Stream_Write_UINT8(s, byte);
}
....
}
ΠΠΎΡΠ»Π΅Π΄Π½ΠΈΠ΅ Π΄Π²Π° ΡΡΠ»ΠΎΠ²ΠΈΡ ΠΎΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²Ρ: Π²ΠΈΠ΄ΠΈΠΌΠΎ, ΠΊΡΠΎ-ΡΠΎ Π·Π°Π±ΡΠ» ΠΏΡΠΎΠ²Π΅ΡΠΈΡΡ ΠΈΡ ΠΏΠΎΡΠ»Π΅ ΠΊΠΎΠΏΠΈΡΠΎΠ²Π°Π½ΠΈΡ. ΠΠΎ ΠΊΠΎΠ΄Ρ Π·Π°ΠΌΠ΅ΡΠ½ΠΎ, ΡΡΠΎ ΠΏΠΎΡΠ»Π΅Π΄Π½ΡΡ ΡΠ°ΡΡΡ ΡΠ°Π±ΠΎΡΠ°Π΅Ρ Ρ ΡΠ΅ΡΡΡΠ΅Ρ Π±Π°ΠΉΡΠ½ΡΠΌΠΈ Π·Π½Π°ΡΠ΅Π½ΠΈΡΠΌΠΈ, ΠΏΠΎΡΡΠΎΠΌΡ ΠΌΠΎΠΆΠ½ΠΎ ΠΏΡΠ΅Π΄ΠΏΠΎΠ»ΠΎΠΆΠΈΡΡ, ΡΡΠΎ ΠΏΠΎΡΠ»Π΅Π΄Π½Π΅Π΅ ΡΡΠ»ΠΎΠ²ΠΈΠ΅ Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π±ΡΡΡ value <= 0x3FFFFFFF.
ΠΡΠ»Π° Π½Π°ΠΉΠ΄Π΅Π½Π° ΠΈ Π΄ΡΡΠ³Π°Ρ ΠΎΡΠΈΠ±ΠΊΠ° ΡΠ°ΠΊΠΎΠ³ΠΎ ΡΠΈΠΏΠ°:
- V517 The use of ‘if (A) {…} else if (A) {…}’ pattern was detected. There is a probability of logical error presence. Check lines: 169, 173. file.c 169
ΠΡΠΎΠ²Π΅ΡΠΊΠ° Π²Ρ ΠΎΠ΄Π½ΡΡ Π΄Π°Π½Π½ΡΡ
Π€ΡΠ°Π³ΠΌΠ΅Π½Ρ 1
TRIO_PUBLIC_STRING int
trio_append
TRIO_ARGS2((target, source),
char *target,
TRIO_CONST char *source)
{
assert(target);
assert(source);
return (strcat(target, source) != NULL);
}
ΠΡΠΎΠ²Π΅ΡΠΊΠ° ΡΠ΅Π·ΡΠ»ΡΡΠ°ΡΠ° Π²ΡΠΏΠΎΠ»Π½Π΅Π½ΠΈΡ ΡΡΠ½ΠΊΡΠΈΠΈ Π² ΡΡΠΎΠΌ ΠΏΡΠΈΠΌΠ΅ΡΠ΅ Π½Π΅ΠΊΠΎΡΡΠ΅ΠΊΡΠ½Π°. Π€ΡΠ½ΠΊΡΠΈΡ strcat Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅Ρ ΡΠΊΠ°Π·Π°ΡΠ΅Π»Ρ Π½Π° ΠΊΠΎΠ½Π΅ΡΠ½ΡΠΉ Π²Π°ΡΠΈΠ°Π½Ρ ΡΡΡΠΎΠΊΠΈ, Ρ.Π΅. ΠΏΠ΅ΡΠ²ΡΠΉ ΠΏΠ΅ΡΠ΅Π΄Π°Π½Π½ΡΠΉ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡ. Π Π΄Π°Π½Π½ΠΎΠΌ ΡΠ»ΡΡΠ°Π΅ ΡΡΠΎ target. ΠΠ΄Π½Π°ΠΊΠΎ Π΅ΡΠ»ΠΈ ΠΎΠ½ ΡΠ°Π²Π΅Π½ NULL, ΡΠΎ ΠΏΡΠΎΠ²Π΅ΡΡΡΡ Π΅Π³ΠΎ ΠΏΠΎΠ·Π΄Π½ΠΎ, ΡΠ°ΠΊ ΠΊΠ°ΠΊ Π² ΡΡΠ½ΠΊΡΠΈΠΈ strcat ΠΏΡΠΎΠΈΠ·ΠΎΠΉΠ΄ΡΡ Π΅Π³ΠΎ ΡΠ°Π·ΡΠΌΠ΅Π½ΠΎΠ²Π°Π½ΠΈΠ΅.
Π€ΡΠ°Π³ΠΌΠ΅Π½Ρ 2
typedef struct rdp_glyph_cache rdpGlyphCache;
struct rdp_glyph_cache
{
....
GLYPH_CACHE glyphCache[10];
....
};
void glyph_cache_free(rdpGlyphCache* glyphCache)
{
....
GLYPH_CACHE* cache = glyphCache->glyphCache;
if (cache)
{
....
}
....
}
Π ΡΡΠΎΠΌ ΡΠ»ΡΡΠ°Π΅ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΠΎΠΉ cache ΠΏΡΠΈΡΠ²Π°ΠΈΠ²Π°Π΅ΡΡΡ Π°Π΄ΡΠ΅Ρ ΡΡΠ°ΡΠΈΡΠ΅ΡΠΊΠΎΠ³ΠΎ ΠΌΠ°ΡΡΠΈΠ²Π° glyphCache->glyphCache. Π’Π°ΠΊΠΈΠΌ ΠΎΠ±ΡΠ°Π·ΠΎΠΌ, ΠΏΡΠΎΠ²Π΅ΡΠΊΡ if (cache) ΠΌΠΎΠΆΠ½ΠΎ ΠΎΠΏΡΡΡΠΈΡΡ.
ΠΡΠΈΠ±ΠΊΠ° ΡΠΏΡΠ°Π²Π»Π΅Π½ΠΈΡ ΡΠ΅ΡΡΡΡΠ°ΠΌΠΈ
BOOL certificate_data_replace(rdpCertificateStore* certificate_store,
rdpCertificateData* certificate_data)
{
HANDLE fp;
....
fp = CreateFileA(certificate_store->file, GENERIC_READ | GENERIC_WRITE, 0,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
....
if (size < 1)
{
CloseHandle(fp);
return FALSE;
}
....
if (!data)
{
fclose(fp);
return FALSE;
}
....
}
ΠΠ΅ΡΠΊΡΠΈΠΏΡΠΎΡ ΡΠ°ΠΉΠ»Π° fp, ΡΠΎΠ·Π΄Π°Π½Π½ΡΠΉ Π²ΡΠ·ΠΎΠ²ΠΎΠΌ ΡΡΠ½ΠΊΡΠΈΠΈ CreateFile, ΠΏΠΎ ΠΎΡΠΈΠ±ΠΊΠ΅ Π·Π°ΠΊΡΡΡ ΡΡΠ½ΠΊΡΠΈΠ΅ΠΉ fclose ΠΈΠ· ΡΡΠ°Π½Π΄Π°ΡΡΠ½ΠΎΠΉ Π±ΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊΠΈ, Π° Π½Π΅ CloseHandle.
ΠΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²ΡΠ΅ ΡΡΠ»ΠΎΠ²ΠΈΡ
void NdrComplexStructBufferSize(PMIDL_STUB_MESSAGE pStubMsg,
unsigned char* pMemory, PFORMAT_STRING pFormat)
{
....
if (conformant_array_description)
{
ULONG size;
unsigned char array_type;
array_type = conformant_array_description[0];
size = NdrComplexStructMemberSize(pStubMsg, pFormat);
WLog_ERR(TAG, "warning: NdrComplexStructBufferSize array_type: "
"0x%02X unimplemented", array_type);
NdrpComputeConformance(pStubMsg, pMemory + size,
conformant_array_description);
NdrpComputeVariance(pStubMsg, pMemory + size,
conformant_array_description);
MaxCount = pStubMsg->MaxCount;
ActualCount = pStubMsg->ActualCount;
Offset = pStubMsg->Offset;
}
if (conformant_array_description)
{
unsigned char array_type;
array_type = conformant_array_description[0];
pStubMsg->MaxCount = MaxCount;
pStubMsg->ActualCount = ActualCount;
pStubMsg->Offset = Offset;
WLog_ERR(TAG, "warning: NdrComplexStructBufferSize array_type: "
"0x%02X unimplemented", array_type);
}
....
}
ΠΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎ, ΡΡΠΎΡ ΠΏΡΠΈΠΌΠ΅Ρ Π½Π΅ ΡΠ²Π»ΡΠ΅ΡΡΡ ΠΎΡΠΈΠ±ΠΊΠΎΠΉ. ΠΠ΄Π½Π°ΠΊΠΎ ΠΎΠ±Π° ΡΡΠ»ΠΎΠ²ΠΈΡ ΡΠΎΠ΄Π΅ΡΠΆΠ°Ρ ΠΎΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²ΡΠ΅ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΡ, ΠΎΠ΄Π½ΠΎ ΠΈΠ· ΠΊΠΎΡΠΎΡΡΡ , ΡΠΊΠΎΡΠ΅Π΅ Π²ΡΠ΅Π³ΠΎ, ΠΌΠΎΠΆΠ½ΠΎ ΡΠ±ΡΠ°ΡΡ.
ΠΡΠΈΡΡΠΊΠ° Π½ΡΠ»Π΅Π²ΡΡ ΡΠΊΠ°Π·Π°ΡΠ΅Π»Π΅ΠΉ
WINSCARDAPI LONG WINAPI PCSC_SCardListReadersW(
SCARDCONTEXT hContext,
LPCWSTR mszGroups,
LPWSTR mszReaders,
LPDWORD pcchReaders)
{
LPSTR mszGroupsA = NULL;
....
mszGroups = NULL; /* mszGroups is not supported by pcsc-lite */
if (mszGroups)
ConvertFromUnicode(CP_UTF8,0, mszGroups, -1,
(char**) &mszGroupsA, 0,
NULL, NULL);
status = PCSC_SCardListReaders_Internal(hContext, mszGroupsA,
(LPSTR) &mszReadersA,
pcchReaders);
if (status == SCARD_S_SUCCESS)
{
....
}
free(mszGroupsA);
....
}
Π ΡΡΠ½ΠΊΡΠΈΡ free ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠ΅ΡΠ΅Π΄Π°Π²Π°ΡΡ Π½ΡΠ»Π΅Π²ΠΎΠΉ ΡΠΊΠ°Π·Π°ΡΠ΅Π»Ρ ΠΈ Π°Π½Π°Π»ΠΈΠ·Π°ΡΠΎΡ ΠΎΠ± ΡΡΠΎΠΌ Π·Π½Π°Π΅Ρ. ΠΠΎ Π΅ΡΠ»ΠΈ Π²ΡΡΠ²Π»ΡΠ΅ΡΡΡ ΡΠΈΡΡΠ°ΡΠΈΡ, ΠΏΡΠΈ ΠΊΠΎΡΠΎΡΠΎΠΉ ΡΠΊΠ°Π·Π°ΡΠ΅Π»Ρ Π²ΡΠ΅Π³Π΄Π° ΠΏΠ΅ΡΠ΅Π΄Π°ΡΡΡΡ Π½ΡΠ»Π΅Π²ΡΠΌ, ΠΊΠ°ΠΊ Π² ΡΡΠΎΠΌ ΡΡΠ°Π³ΠΌΠ΅Π½ΡΠ΅, Π±ΡΠ΄Π΅Ρ Π²ΡΠ΄Π°Π½ΠΎ ΠΏΡΠ΅Π΄ΡΠΏΡΠ΅ΠΆΠ΄Π΅Π½ΠΈΠ΅.
Π£ΠΊΠ°Π·Π°ΡΠ΅Π»Ρ mszGroupsA ΠΈΠ·Π½Π°ΡΠ°Π»ΡΠ½ΠΎ ΡΠ°Π²Π΅Π½ NULL ΠΈ Π±ΠΎΠ»ΡΡΠ΅ Π½ΠΈΠ³Π΄Π΅ Π½Π΅ ΠΈΠ½ΠΈΡΠΈΠ°Π»ΠΈΠ·ΠΈΡΡΠ΅ΡΡΡ. ΠΠ΄ΠΈΠ½ΡΡΠ²Π΅Π½Π½Π°Ρ Π²Π΅ΡΠ²Ρ ΠΊΠΎΠ΄Π°, Π³Π΄Π΅ ΡΠΊΠ°Π·Π°ΡΠ΅Π»Ρ ΠΌΠΎΠ³ ΠΈΠ½ΠΈΡΠΈΠ°Π»ΠΈΠ·ΠΈΡΠΎΠ²Π°ΡΡΡΡ, ΡΠ²Π»ΡΠ΅ΡΡΡ Π½Π΅Π΄ΠΎΡΡΠΈΠΆΠΈΠΌΠΎΠΉ.
ΠΡΠ»ΠΈ ΠΈ Π΄ΡΡΠ³ΠΈΠ΅ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΡ ΡΡΠΎ ΡΠΈΠΏΠ°:
- V575 The null pointer is passed into ‘free’ function. Inspect the first argument. license.c 790
- V575 The null pointer is passed into ‘free’ function. Inspect the first argument. rdpsnd_alsa.c 575
Π‘ΠΊΠΎΡΠ΅Π΅ Π²ΡΠ΅Π³ΠΎ, ΠΏΠΎΠ΄ΠΎΠ±Π½ΡΠ΅ Π·Π°Π±ΡΡΡΠ΅ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΠ΅ Π²ΠΎΠ·Π½ΠΈΠΊΠ°ΡΡ Π² ΠΏΡΠΎΡΠ΅ΡΡΠ΅ ΡΠ΅ΡΠ°ΠΊΡΠΎΡΠΈΠ½Π³Π° ΠΈ ΠΈΡ ΠΌΠΎΠΆΠ½ΠΎ ΠΏΡΠΎΡΡΠΎ ΡΠ΄Π°Π»ΠΈΡΡ.
ΠΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΠ΅ ΠΏΠ΅ΡΠ΅ΠΏΠΎΠ»Π½Π΅Π½ΠΈΠ΅
// openssl/x509.h
ASN1_TIME *X509_gmtime_adj(ASN1_TIME *s, long adj);
struct _MAKECERT_CONTEXT
{
....
int duration_years;
int duration_months;
};
typedef struct _MAKECERT_CONTEXT MAKECERT_CONTEXT;
int makecert_context_process(MAKECERT_CONTEXT* context, ....)
{
....
if (context->duration_months)
X509_gmtime_adj(after, (long)(60 * 60 * 24 * 31 *
context->duration_months));
else if (context->duration_years)
X509_gmtime_adj(after, (long)(60 * 60 * 24 * 365 *
context->duration_years));
....
}
ΠΡΠΈΠ²Π΅Π΄Π΅Π½ΠΈΠ΅ ΡΠ΅Π·ΡΠ»ΡΡΠ°ΡΠ° ΠΊ long Π½Π΅ ΡΠ²Π»ΡΠ΅ΡΡΡ Π·Π°ΡΠΈΡΠΎΠΉ ΠΎΡ ΠΏΠ΅ΡΠ΅ΠΏΠΎΠ»Π½Π΅Π½ΠΈΡ, ΡΠ°ΠΊ ΠΊΠ°ΠΊ ΡΠ°ΠΌΠΎ Π²ΡΡΠΈΡΠ»Π΅Π½ΠΈΠ΅ ΠΏΡΠΎΠΈΡΡ ΠΎΠ΄ΠΈΡ Ρ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ΠΌ ΡΠΈΠΏΠ° int.
Π Π°Π·ΡΠΌΠ΅Π½ΠΎΠ²Π°Π½ΠΈΠ΅ ΡΠΊΠ°Π·Π°ΡΠ΅Π»Ρ Π² ΠΈΠ½ΠΈΡΠΈΠ°Π»ΠΈΠ·Π°ΡΠΈΠΈ
static UINT gdi_SurfaceCommand(RdpgfxClientContext* context,
const RDPGFX_SURFACE_COMMAND* cmd)
{
....
rdpGdi* gdi = (rdpGdi*) context->custom;
if (!context || !cmd)
return ERROR_INVALID_PARAMETER;
....
}
ΠΠ΄Π΅ΡΡ ΡΠΊΠ°Π·Π°ΡΠ΅Π»Ρ context ΡΠ°Π·ΡΠΌΠ΅Π½ΠΎΠ²ΡΠ²Π°Π΅ΡΡΡ Π² ΠΈΠ½ΠΈΡΠΈΠ°Π»ΠΈΠ·Π°ΡΠΈΠΈ β ΡΠ°Π½ΡΡΠ΅, ΡΠ΅ΠΌ ΠΏΡΠΎΠΈΡΡ ΠΎΠ΄ΠΈΡ Π΅Π³ΠΎ ΠΏΡΠΎΠ²Π΅ΡΠΊΠ°.
ΠΡΠ»ΠΈ Π½Π°ΠΉΠ΄Π΅Π½Ρ ΠΈ Π΄ΡΡΠ³ΠΈΠ΅ ΠΎΡΠΈΠ±ΠΊΠΈ ΡΠ°ΠΊΠΎΠ³ΠΎ ΡΠΈΠΏΠ°:
- V595 The ‘ntlm’ pointer was utilized before it was verified against nullptr. Check lines: 236, 255. ntlm.c 236
- V595 The ‘context’ pointer was utilized before it was verified against nullptr. Check lines: 1003, 1007. rfx.c 1003
- V595 The ‘rdpei’ pointer was utilized before it was verified against nullptr. Check lines: 176, 180. rdpei_main.c 176
- V595 The ‘gdi’ pointer was utilized before it was verified against nullptr. Check lines: 121, 123. xf_gfx.c 121
ΠΠ΅ΡΡΠΌΡΡΠ»Π΅Π½Π½ΠΎΠ΅ ΡΡΠ»ΠΎΠ²ΠΈΠ΅
int rdp_server_transition_to_state(rdpRdp* rdp, int state)
{
....
switch (state)
{
....
case CONNECTION_STATE_ACTIVE:
rdp->state = CONNECTION_STATE_ACTIVE; // <=
....
if (rdp->state >= CONNECTION_STATE_ACTIVE) // <=
{
IFCALLRET(client->Activate, client->activated, client);
if (!client->activated)
return -1;
}
....
}
....
}
ΠΠ΅Π³ΠΊΠΎ Π·Π°ΠΌΠ΅ΡΠΈΡΡ, ΡΡΠΎ ΠΏΠ΅ΡΠ²ΠΎΠ΅ ΡΡΠ»ΠΎΠ²ΠΈΠ΅ Π½Π΅ ΠΈΠΌΠ΅Π΅Ρ ΡΠΌΡΡΠ»Π° ΠΈΠ·-Π·Π° ΠΏΡΠΈΡΠ²Π°ΠΈΠ²Π°Π½ΠΈΡ ΡΠΎΠΎΡΠ²Π΅ΡΡΡΠ²ΡΡΡΠ΅Π³ΠΎ Π·Π½Π°ΡΠ΅Π½ΠΈΡ ΡΠ°Π½Π΅Π΅.
ΠΠ΅ΠΊΠΎΡΡΠ΅ΠΊΡΠ½ΡΠΉ ΡΠ°Π·Π±ΠΎΡ ΡΡΡΠΎΠΊΠΈ
static BOOL check_no_proxy(....)
{
....
int sub;
int rc = sscanf(range, "%u", &sub);
if ((rc == 1) && (rc >= 0))
{
....
}
....
}
ΠΠ½Π°Π»ΠΈΠ·Π°ΡΠΎΡ Π΄Π»Ρ ΡΡΠΎΠ³ΠΎ ΡΡΠ°Π³ΠΌΠ΅Π½ΡΠ° Π²ΡΠ΄Π°Π΅Ρ ΡΡΠ°Π·Ρ 2 ΠΏΡΠ΅Π΄ΡΠΏΡΠ΅ΠΆΠ΄Π΅Π½ΠΈΡ. Π‘ΠΏΠ΅ΡΠΈΡΠΈΠΊΠ°ΡΠΎΡ %u ΠΎΠΆΠΈΠ΄Π°Π΅Ρ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΡ ΡΠΈΠΏΠ° unsigned int, Π½ΠΎ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½Π°Ρ sub ΠΈΠΌΠ΅Π΅Ρ ΡΠΈΠΏ int. ΠΠ°Π»Π΅Π΅ ΠΌΡ Π²ΠΈΠ΄ΠΈΠΌ ΠΏΠΎΠ΄ΠΎΠ·ΡΠΈΡΠ΅Π»ΡΠ½ΡΡ ΠΏΡΠΎΠ²Π΅ΡΠΊΡ: ΡΡΠ»ΠΎΠ²ΠΈΠ΅ ΡΠΏΡΠ°Π²Π° Π½Π΅ ΠΈΠΌΠ΅Π΅Ρ ΡΠΌΡΡΠ»Π°, ΡΠ°ΠΊ ΠΊΠ°ΠΊ Π² Π½Π°ΡΠ°Π»Π΅ ΠΈΠ΄Π΅Ρ ΡΡΠ°Π²Π½Π΅Π½ΠΈΠ΅ Ρ Π΅Π΄ΠΈΠ½ΠΈΡΠ΅ΠΉ. ΠΠ΅ Π·Π½Π°Ρ, ΡΡΠΎ ΠΈΠΌΠ΅Π» Π² Π²ΠΈΠ΄Ρ Π°Π²ΡΠΎΡ ΡΡΠΎΠ³ΠΎ ΠΊΠΎΠ΄Π°, Π½ΠΎ ΡΡΡ ΡΠ²Π½ΠΎ ΡΡΠΎ-ΡΠΎ Π½Π΅ ΡΠ°ΠΊ.
ΠΠ΅ΡΠΏΠΎΡΡΠ΄ΠΎΡΠ΅Π½Π½ΡΠ΅ ΠΏΡΠΎΠ²Π΅ΡΠΊΠΈ
BOOL ntlm_authenticate(rdpNtlm* ntlm, BOOL* pbContinueNeeded)
{
....
if (status != SEC_E_OK)
{
....
return FALSE;
}
if (status == SEC_I_COMPLETE_NEEDED) // <=
status = SEC_E_OK;
else if (status == SEC_I_COMPLETE_AND_CONTINUE) // <=
status = SEC_I_CONTINUE_NEEDED;
....
}
ΠΡΠΌΠ΅ΡΠ΅Π½Π½ΡΠ΅ ΡΡΠ»ΠΎΠ²ΠΈΡ Π±ΡΠ΄ΡΡ Π²ΡΠ΅Π³Π΄Π° Π»ΠΎΠΆΠ½Ρ, ΡΠ°ΠΊ ΠΊΠ°ΠΊ Π²ΡΠΏΠΎΠ»Π½Π΅Π½ΠΈΠ΅ Π΄ΠΎΠΉΠ΄Π΅Ρ Π΄ΠΎ Π²ΡΠΎΡΠΎΠ³ΠΎ ΡΡΠ»ΠΎΠ²ΠΈΡ ΡΠΎΠ»ΡΠΊΠΎ Π² ΡΠΎΠΌ ΡΠ»ΡΡΠ°Π΅, ΠΊΠΎΠ³Π΄Π° status == SEC_E_OK. ΠΡΠ°Π²ΠΈΠ»ΡΠ½ΡΠΉ ΠΊΠΎΠ΄ ΠΌΠΎΠΆΠ΅Ρ Π²ΡΠ³Π»ΡΠ΄Π΅ΡΡ ΡΠ°ΠΊ:
if (status == SEC_I_COMPLETE_NEEDED)
status = SEC_E_OK;
else if (status == SEC_I_COMPLETE_AND_CONTINUE)
status = SEC_I_CONTINUE_NEEDED;
else if (status != SEC_E_OK)
{
....
return FALSE;
}
ΠΠ°ΠΊΠ»ΡΡΠ΅Π½ΠΈΠ΅
Π’Π°ΠΊΠΈΠΌ ΠΎΠ±ΡΠ°Π·ΠΎΠΌ, ΠΏΡΠΎΠ²Π΅ΡΠΊΠ° ΠΏΡΠΎΠ΅ΠΊΡΠ° Π²ΡΡΠ²ΠΈΠ»Π° ΠΌΠ½ΠΎΠΆΠ΅ΡΡΠ²ΠΎ ΠΏΡΠΎΠ±Π»Π΅ΠΌ, Π½ΠΎ ΡΠΎΠ»ΡΠΊΠΎ Π½Π°ΠΈΠ±ΠΎΠ»Π΅Π΅ ΠΈΠ½ΡΠ΅ΡΠ΅ΡΠ½Π°Ρ ΠΈΡ
ΡΠ°ΡΡΡ Π±ΡΠ»Π° ΠΎΠΏΠΈΡΠ°Π½Π° Π² ΡΡΠ°ΡΡΠ΅. Π Π°Π·ΡΠ°Π±ΠΎΡΡΠΈΠΊΠΈ ΠΏΡΠΎΠ΅ΠΊΡΠ° ΠΌΠΎΠ³ΡΡ ΡΠ°ΠΌΠΈ ΠΏΡΠΎΠ²Π΅ΡΠΈΡΡ ΠΏΡΠΎΠ΅ΠΊΡ, Π·Π°ΠΏΡΠΎΡΠΈΠ² Π²ΡΠ΅ΠΌΠ΅Π½Π½ΡΠΉ ΠΊΠ»ΡΡ Π»ΠΈΡΠ΅Π½Π·ΠΈΠΈ Π½Π° ΡΠ°ΠΉΡΠ΅
ΠΡΠ»ΠΈ Ρ
ΠΎΡΠΈΡΠ΅ ΠΏΠΎΠ΄Π΅Π»ΠΈΡΡΡΡ ΡΡΠΎΠΉ ΡΡΠ°ΡΡΠ΅ΠΉ Ρ Π°Π½Π³Π»ΠΎΡΠ·ΡΡΠ½ΠΎΠΉ Π°ΡΠ΄ΠΈΡΠΎΡΠΈΠ΅ΠΉ, ΡΠΎ ΠΏΡΠΎΡΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ ΡΡΡΠ»ΠΊΡ Π½Π° ΠΏΠ΅ΡΠ΅Π²ΠΎΠ΄: Sergey Larin.
ΠΡΡΠΎΡΠ½ΠΈΠΊ: habr.com