Реализация memset / memcpy / strcpy на C ++ - проверка переполнения буфера

0

Я сделал основные реализации memset/memcpy/strcpy в C++, которые отлично работают. Однако существует ли способ обнаружения переполнения буфера, если я должен был сделать что-то вроде этого:

Пример:

int main()
{
    char *buf = (char *)calloc(10, sizeof(char));
    __strcpy(buf, "Hello World"); 
    // buffer size: 10, copy size: 12 (including '\0') - overflow
}

Реализации (typedef unsigned int UINT):

void *__memset(void *_Dst, int _Val, UINT _Size)
{
    UINT *buf = (UINT *)_Dst;
    while (_Size--)
    {
        *buf++ = (UINT)_Val;
    }
    return _Dst;
}

void *__memcpy(void *_Dst, const void *_Src, UINT _Size)
{
    UINT *buf = (UINT *)_Dst;
    UINT *src = (UINT *)_Src;
    while (_Size--)
    {
        *buf++ = *src++;
    }
    return _Dst;
}

char *__strcpy(char *_Dst, const char *_Src)
{
    while ((*_Dst++ = *_Src++) != '\0');
    return _Dst;
}
  • 2
    реализовать вместо этого strncpy? Кроме того, почему это помечено как C ++, он пахнет очень похоже на C ...
  • 1
    __strcpy - это зарезервированное имя. Не используйте двойные подчеркивания в именах. Кроме того, размеры должны быть size_t , а не unsigned int .
Показать ещё 4 комментария
Теги:

6 ответов

2

Переполнение буфера не обнаруживается в вашей программе. Операционная система обнаруживает их. Вы можете проверить наличие возможных ошибок в коде (если /else, утверждает, исключения). Или вы используете инструменты для профилирования, такие как valgrind.

1

Вы можете обнаружить переполнение, но только если вы также реализуете свои собственные процедуры управления памятью. Мы это делали, когда писали встроенное программное обеспечение, которое запускалось на устройствах без "реальной" операционной системы, прежде чем было очень много хороших инструментов отладки.

Идея состоит в том, чтобы создать собственную оболочку вокруг malloc()calloc() в вашем случае), которая будет выделять несколько байтов, чем запросы вызывающего. Затем настройте несколько "защитных байтов" до и после запрошенной памяти и инициализируйте весь буфер с распознаваемыми данными. Также создайте обертку вокруг free() которая проверяет защитные байты перед освобождением памяти и генерирует ошибку, если они были изменены.

#define GUARD_LEN = 4   // Arbitrary number of guard bytes.
#define GUARD_BYTE = 0xA5  // Arbitrary but recognizable: 10100101b
#define UNUSED_BYTE = 0x96 // Arbitrary but recognizable: 10010110b
#define FREED_BYTE = 0xC3  // Arbitrary but recognizable: 11000011b
#define MAX_ALLOCS = 1024  // Max # of malloc'ed buffers.
struct {
  void *addr;  // Address of malloc'ed buffer
  size_t len;  // Number of requested bytes
} Allocs[MAX_ALLOCS];

// Allocates and initializes memory.
void *chk_malloc(size_t length) {
  // Allocate memory for buffer + guard bytes.
  void *mem = malloc(length + 2*GUARD_LEN);
  if (mem == NULL) {
    return NULL;
  }

  // Initialize: [GUARD][UNUSED_BUFFER][GUARD]
  // Caller usable memory starts after GUARD.
  void *buffer = mem + GUARD_LEN;
  memset(mem, GUARD_BYTE, GUARD_LEN);
  memset(buffer, UNUSED_BYTE, length);
  memset(buffer + length, GUARD_BYTE, GUARD_LEN);

  // Remember the address and length.
  // Simplified for demonstration; you may want this to be smarter.
  for (int i = 0; i < MAX_ALLOCS; ++i) {
    if (Allocs[i].addr == NULL) {
      Allocs[i].addr = buffer;
      Allocs[i].len = length;
      return buffer;
  }
  return NULL;  // Should also indicate MAX_ALLOCS is too small.
}

// Checks that buffer is filled with val.
bool chk_filled(void *buffer, char val, size_t len) {
  for (int i = 0; i < len; ++i) {
    if (buffer[i] != val) {
      return false;
    }
  }
  return true;
}

// Checks for over/underrun and releases memory.
void chk_free(void *buffer) {
  // Find the buffer in the array of alloc'ed buffers.
  for (int i = 0; i < MAX_ALLOCS; ++i) {
    if (Allocs[i].addr == buffer) {
      void *guard = buffer - GUARD_LEN;  // Initial guard bytes.
      if (!chk_filled(guard, GUARD_BYTE, GUARD_LEN)) {
        // Underrun
      }
      end_guard = buffer + Allocs[i].len;    // Terminal guard bytes.
      if (!chk_filled(end_guard, GUARD_BYTE, GUARD_LEN)) {
        // Overrun
      }

      // Mark the buffer as free and release it.
      memset(guard, FREED_BYTE, Allocs[i].len + 2*GUARD_LEN);
      Allocs[i].addr = -Allocs[i].addr;  // See text below.
      free(guard);
      return;
    }
  }
  // Error: attempt to free unalloc'ed memory.
}

В действительности вы, вероятно, захотите, чтобы это было умнее несколькими способами:

  • Возможно, вы не хотите иметь ограничение MAX_ALLOCS.
  • Проверьте выделенную память, которая не освобождается при выходе из программы.
  • Печать Allocs[] при выходе.
  • Распечатайте дополнительную информацию и/или немедленно выйдите, когда обнаружена ошибка.
0

Там может быть реальная реализация C++, если вы используете массивы вместо указателей на char. Вы можете определить свои функции как шаблон и сделать массив массив аргументом шаблона.

template < std::size_t D >
char* strcpy( char ( &dest )[ D ], const char* source )
{
    assert( D > std::strlen( source ) );
    ...
}

Поскольку вам действительно нужен размер адресата, я оставил исходный размер.

int main()
{
    char buf[ 10 ];
    // would assert, if assertions are enabled.
    strcpy( buf, "Hello World" ); 
}
0

Все ваши функции mem * недействительны. Они копируют (или задают) объекты типа unsigned int, когда им приходится копировать (или устанавливать) объекты типа unsigned char. Учтите, что __Size может быть нечетным числом. Эти функции сами не могут проверить переполнение буфера без изменения их объявлений.

Также недействительна даже третья функция

char *__strcpy(char *_Dst, const char *_Src)
{
    while ((*_Dst++ = *_Src++) != '\0');
    return _Dst;
}

внутри указателя функции _Dst был изменен и указывает на завершающий нуль. Этот адрес вы возвращаете из функции, пока вы должны вернуть адрес первого символа строки, на которую указывает _Dst.

То же самое верно для первых двух функций.

0

Вам может понадобиться size буфера, который вы используете, который может использоваться для повторения множества мест для копирования проверки переполнения буфера, если таковой имеется, как показано ниже,

char *__strcpy(char *_Dst, const char *_Src, int size)
{
   while ((*_Dst++ = *_Src++) != '\0' && size--); //Iterate/copy to allocated Bytes
   return _Dst;
}

int main()
{
    int size;
    char *buf = (char *)calloc(size, sizeof(char)); // Here you know the size of buf
    __strcpy(buf, "Hello World", size);  // Send size as parameter
   // buffer size: 10, copy size: 12 (including '\0') - overflow
}
0

Более безопасный способ обнаружения переполнения буфера заключается в том, чтобы вместо этого выполнить собственную реализацию calloc. Предоставьте несколько пробелов в байтах до и после возвращаемого блока, установите их на известное значение (NOT 0 или 255) и при free проверке, чтобы они были нетронутыми. Кроме того, после free звонка вы должны перезаписать весь блок (включая прописку с обеих сторон), чтобы проверить двойные free звонки.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню