Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kernel32.WIN32_STREAM_ID has invalid defenition #516

Open
uom42 opened this issue Feb 22, 2025 · 2 comments
Open

Kernel32.WIN32_STREAM_ID has invalid defenition #516

uom42 opened this issue Feb 22, 2025 · 2 comments

Comments

@uom42
Copy link

uom42 commented Feb 22, 2025

Describe the bug and how to reproduce

currently declared WIN32_STREAM_ID has size = 24 byte, which makes it impossible to use it in Kernel32.BackupRead to read NTFS file streams.

Correct size must be 20 bytes.
Problem is well described in https://stackoverflow.com/questions/604960/how-to-read-and-modify-ntfs-alternate-data-streams-using-net

The issue is that the compiler wants to make sure that the values within these structures are always aligned on the proper boundary. Four-byte values should be at addresses divisible by 4, 8-byte values should be at boundaries divisible by 8, and so on. Now imagine what would happen if you were to create an array of Win32StreamID structures. All of the fields in the first instance of the array would be properly aligned. For example, since the Size field follows two 32-bit integers, it would be 8 bytes from the start of the array, perfect for an 8-byte value. However, if the structure were 20-bytes in size, the second instance in the array would not have all of its members properly aligned. The integer values would all be fine, but the long value would be 28 bytes from the start of the array, a value not evenly divisible by 8. To fix this, the compiler pads the structure to a size of 24, such that all of the fields will always be properly aligned (assuming the array itself is). If the compiler's doing the right thing, you might be wondering why I'm concerned about this. You'll see why if you look at the code in Figure 2. In order to get around the first marshaling issue I described, I do in fact leave the cStreamName out of the Win32StreamID structure. I use BackupRead to read in enough bytes to fill my Win32StreamID structure, and then I examine the structure's dwStreamNameSize field. Now that I know how long the name is, I can use BackupRead again to read in the string's value from the file. That's all well and dandy, but if Marshal.SizeOf returns 24 for my Win32StreamID structure instead of 20, I'll be attempting to read too much data. To avoid this, I need to make sure that the size of Win32StreamID is in fact 20 and not 24. This can be accomplished in two different ways using fields on the StructLayoutAttribute that adorns the structure. The first is to use the Size field, which dictates to the runtime exactly how big the structure should be:
[StructLayout(LayoutKind.Sequential, Size = 20)]

The second option is to use the Pack field. Pack indicates the packing size that should be used when the LayoutKind.Sequential value is specified and controls the alignment of the fields within the structure. The default packing size for a managed structure is 8. If I change that to 4, I get the 20-byte structure I'm looking for (and as I'm not actually using this in an array, I don't lose efficiency or stability that might result from such a packing change):
[StructLayout(LayoutKind.Sequential, Pack = 4)]

Expected behavior

structure must be declared as:


[StructLayout (LayoutKind.Sequential, Pack = 4)]
public struct WIN32_STREAM_ID
{
				public Kernel32.BACKUP_STREAM_ID dwStreamId;
				public Kernel32.BACKUP_STREAM_ATTR dwStreamAttributes;
				public long Size;
				public uint dwStreamNameSize;
				// WCHAR cStreamName[1];
}

or


[StructLayout (LayoutKind.Sequential, Size = 20)]
public struct WIN32_STREAM_ID
{
				public Kernel32.BACKUP_STREAM_ID dwStreamId;
				public Kernel32.BACKUP_STREAM_ATTR dwStreamAttributes;
				public long Size;
				public uint dwStreamNameSize;
				// WCHAR cStreamName[1];
}

@dahall
Copy link
Owner

dahall commented Feb 23, 2025

I'm not sure I agree. Using the C++ compiler, both 32 and 64-bit versions of the structure are 24 bytes with an 8 byte alignment. The actual structure only consumes 22 bytes. If size is set to 20, then the cStreamName will never be carried. This deserves more investigation and testing.

Image

@dahall
Copy link
Owner

dahall commented Feb 23, 2025

I would use it as follows (not tested as I don't have a tape drive):

using var hFile = CreateFile(...);
using SafeHGlobalMemoryHandle mem = new(4096); // use appropriate size here
bool ret = BackupRead(hFile, mem, mem.Size, out var bytesRead, false, false, out var ctx);
WIN32_STREAM_ID id = mem.ToStructure<WIN32_STREAM_ID>();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants