Blog / January 29, 2024 /
There are multiple structures in Windows that contain fixed sized arrays. The instance I came across recently was the KERB_QUERY_TKT_CACHE_RESPONSE
struct, which looks like this:
typedef struct _KERB_QUERY_TKT_CACHE_RESPONSE {
KERB_PROTOCOL_MESSAGE_TYPE MessageType;
ULONG CountOfTickets;
KERB_TICKET_CACHE_INFO Tickets[ANYSIZE_ARRAY];
} KERB_QUERY_TKT_CACHE_RESPONSE, *PKERB_QUERY_TKT_CACHE_RESPONSE;
ANYSIZE_ARRAY
is defined as 1
in winnt.h
, but the reality is that the array will be of size CountOfTickets
. This value obviously cannot be known at compile time. Translating these structs to C# can be a bit of a pain because although you can define fixed-sized arrays in an unsafe structure, you can only do so for blittable value types. That means we cannot have a fixed-sized array of another struct.
internal struct KERB_QUERY_TKT_CACHE_RESPONSE
{
public KERB_PROTOCOL_MESSAGE_TYPE MessageType;
public uint CountOfTickets;
public unsafe fixed KERB_TICKET_CACHE_INFO Tickets[1]; // <-- not valid
}
The solution most C# developers go with is to use a pointer type instead, and walk over the memory a little like this:
for (var i = 0; i < cache.CountOfTickets; i++)
{
// deference ticket info
var ticket = *(KERB_TICKET_CACHE_INFO*)ticketPtr;
// do something with ticket
// increment pointer by size of ticket struct
ticketPtr = Unsafe.Add<byte>(ticketPtr, Marshal.SizeOf<KERB_TICKET_CACHE_INFO>());
}
I’m a fan of “unsafe” code, but the problem with pointer mathematics is that it’s quite easy to get wrong. There are two other methods that I know of to handle this type of data. I’m not saying they’re safer or better, just different.
The first is to create a new managed array using the CountOfTickets
value, and copy all the ticket data into it in one go.
// create a new array to hold all tickets
var tickets = new KERB_TICKET_CACHE_INFO[cache.CountOfTickets];
// get pointer to 0th position in array
fixed (void* arrayPtr = &tickets[0])
{
// calculate size of all tickets
var size = Marshal.SizeOf<KERB_TICKET_CACHE_INFO>() * cache.CountOfTickets;
// yeet all the data at once
Buffer.MemoryCopy(ticketPtr, arrayPtr, size, size);
}
The second can be done in a single line, by utilising the relatively new Span
feature. Note that this isn’t readily available in .NET Framework projects without installing the System.Memory
and System.Buffers
NuGet packages.
var tickets = new Span<KERB_TICKET_CACHE_INFO>(ticketPtr, (int)cache.CountOfTickets);
Taking a span is memory efficient because it takes a slice of existing memory, rather than allocating any new memory. However, in scenarios like the above, the ticket buffer has to be freed with LsaFreeReturnBuffer
. If we did that, all the data that the span is referencing would be lost. Spans do have a .ToArray()
method which creates a copy of the data that would then fall under the scope of the garbage collector.
Happy coding.