将结构编码为字节到缓冲区时发生溢出

问题描述 投票:0回答:1

我编写了这个函数,该函数应该将数据结构写入字节缓冲区,然后返回已写入的数量。该函数执行以下操作:

  1. 询问一个字节缓冲区(char *)作为输入,其大小和偏移量从哪里开始写入。例如,偏移量 0x00 将从缓冲区的开头开始。然后要求数据对本例中的结构进行编码。
  2. 将此处称为 Unit_t 的通用数据收集到另一个名为 UnitSet 的对象中,该对象将所有唯一单位收集到动态数组中。
  3. 如果需要,增加缓冲区的大小,在我的测试用例中,我给出了 1 个字节的缓冲区,所以这种情况就会发生。
  4. 然后写入该数据的主体,即图块,然后是单元的主体。
  5. 最终输出将如下所示 =
    [array of tiles] + [array of units]

问题 在最后一次迭代中,单元在图块之后进行编码,我收到 *bytes 的缓冲区溢出错误,在使用调试器和 asan 检查后,我在第五个单元编码后收到错误。

我认为我搞乱了写入数据的偏移量的计算,因为写入文件的输出缓冲区已满或垃圾,而不是我想要的数据,但我无法说出出了什么问题,因为这些转换对我来说看起来是正确的。

我期望的是跳转到我需要写入的字节,然后转换成我要写入的数据,然后我使用数据大小进行写入。

更多信息

  • TileSet_s 结构不是溢出的结构,我对该结构进行了单元测试。
  • 我首先在
    for (TileSet_Size_t ti = 0; ti < tileset->count; ++ti)
    上遇到了相同的错误,但我使用了不正确的铸造和偏移计算,修复后错误移到了突出显示的行上。这就是为什么我认为可能所有偏移计算都是错误的。

如果问题不是字节偏移或转换,并且比我想象的更复杂,我将重现一个示例并共享它以进行调试。

#define TILE_ENCODED_SIZE       ((sizeof(uint16_t) * 10) + 1)

typedef double Unit_t;

size_t
    TileSet_encode (struct TileSet_s *tileset,
        bytes_t **bytes, size_t bytes_offset, size_t bytes_size)
{
    // next we collect the vertices, duplicated aren't stored so we need to collect
    // them and then calculate.
    struct UnitSet_s set;

    if (!UnitSet_init(&set))
    {
        return 0;
    }

    for (TileSet_Size_t ti = 0; ti < tileset->count; ++ti)
    {       // translation of this stuff:
            // iterate each tile and iterate each vertex of each tile.
        for (unsigned short vi = 0; vi < TILE_VERTICES_MAX; ++vi)
        {       // dump the vertex into the set, if we fail terminate operation.
            if (UnitSet_add(&set, tileset->tilearray[ti]->tiledata.vertices[vi]) == UNIT_SET_NOMEM)
            {
                UnitSet_destroy(&set);
                return 0;
            }
        }
    }

    size_t size_tiles = tileset->count * TILE_ENCODED_SIZE;
    size_t size_vertices = sizeof(Unit_t) * set.list.length;
    size_t bytes_to_write = size_tiles + size_vertices;

    if ((bytes_size - bytes_offset) < bytes_to_write)
    {       // now we know how much space the whole thing takes.
            // ensure to increase space if needed.

        bytes_size = (bytes_size - bytes_offset) + bytes_to_write;
        bytes_t *newbytes = realloc(*bytes, sizeof(**bytes) * (bytes_offset + bytes_size));

        if (!newbytes)
        {       // failed to increase bytes buffer.
            return 0;
        }

        *bytes = newbytes;
    }

    for (TileSet_Size_t ti = 0; ti < tileset->count; ++ti)
    {       // encodes tiles.
        size_t tile_offset = bytes_offset + (ti * TILE_ENCODED_SIZE);

        struct Tile_s *tile = &tileset->tilearray[ti]->tiledata;
        *((TileSet_Id_t *)
            &((*bytes)[tile_offset])) = tileset->tilearray[ti]->id;

        for (size_t vi = 0; vi < TILE_VERTICES_MAX; ++vi)
        {
            ((uint16_t *)
                &((*bytes)[tile_offset + 1]))[sizeof(uint16_t) * vi]
                = (uint16_t) UnitSet_search(&set.list, tile->vertices[vi], 0, set.list.length - 1);
        }
    }

    for (size_t vi = 0; vi < set.list.length; ++vi)
    {       // encode vertices.

        // **********************************************************
        // ERROR HERE! Buffer Overflow at *bytes after the fifth unit !!

        ((Unit_t *)
            &((*bytes)[bytes_offset + size_tiles]))[sizeof(Unit_t) * vi]
            = set.list.array[vi];
        
        // **********************************************************
    }

    UnitSet_destroy(&set);

    return bytes_to_write;
}

我尝试用asan调试它并更改转换。

c buffer-overflow
1个回答
0
投票

指针双关很可能会调用未定义的行为,因为您可能违反严格的别名规则。

要将结构打包到 char 缓冲区中,反之亦然,您需要使用

memcpy

typedef struct 
{
    double d;
    char c;
    uint64_t u64;
}struct_t;

void *toPackedStream(struct_t *s, void *buff)
{
    unsigned char *ucbuff = buff;
    memcpy(ucbuff, &s -> d, sizeof(s -> d));
    ucbuff += sizeof(s -> d);
    *ucbuff++ = s -> c;
    memcpy(ucbuff, &s -> u64, sizeof(s -> u64));
    return buff;
}

struct_t *fromPackedStream(struct_t *s, void *buff)
{
    unsigned char *ucbuff = buff;
    memcpy(&s -> d, ucbuff, sizeof(s -> d));
    ucbuff += sizeof(s -> d);
    s -> c = *ucbuff++;
    memcpy(&s -> u64, ucbuff, sizeof(s -> u64));
    return s;
}

您可以通过指针字符大小的数据安全访问,但不能通过其他类型访问。

© www.soinside.com 2019 - 2024. All rights reserved.