-
Notifications
You must be signed in to change notification settings - Fork 677
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
[Validator] Check size of PSV. #6924
base: main
Are you sure you want to change the base?
Conversation
Check size of PSV part matches the PSVVersion. Updated DxilPipelineStateValidation::ReadOrWrite to read based on initInfo.PSVVersion. And return fail when size mismatch in RWMode::Read. Fixes microsoft#6817
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should not block loading of newer PSV versions. Need to do this only in the validator code.
@@ -1078,6 +1083,9 @@ DxilPipelineStateValidation::ReadOrWrite(const void *pBits, uint32_t *pSize, | |||
if (mode == RWMode::CalcSize) { | |||
*pSize = rw.GetSize(); | |||
m_pPSVRuntimeInfo1 = nullptr; // clear ptr to tempRuntimeInfo | |||
} else if (mode == RWMode::Read) { | |||
if (rw.GetSize() != rw.GetOffset()) | |||
return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't want to fail the load when the size isn't equal to the version, otherwise, the forward compatibility will be broken. We only want to verify this in the validator, to ensure that we only validate known versions of PSV.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to return rw.GetOffset() and do the compare in validator.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll fail the load when PSVVersion > MAX_PSV_VERSION anyway.
So, fail the load when size not match should be fine as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PSVVersion could send as default. So fail the load will not work :(
if (initInfo.PSVVersion > 1) | ||
AssignDerived(&m_pPSVRuntimeInfo2, m_pPSVRuntimeInfo0, | ||
m_uPSVRuntimeInfoSize); // failure ok | ||
if (initInfo.PSVVersion > 2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These should be unnecessary. The scenarios are:
- CalcSize or Write mode:
m_uPSVRuntimeInfoSize
based onPSVVersion
ininitInfo
,AssignDerived
will only assign when struct fits in size. - Read mode:
m_uPSVRuntimeInfoSize
determines the struct version available, PSVVersion in initInfo is not used for this, and there is no independent source for PSVVersion for PSV readers.
Validator will want to validate that m_uPSVRuntimeInfoSize
matches the one computed by initInfo.RuntimeInfoSize()
where initInfo.PSVVersion
has been set according to validator version. It's probably best if this is done manually by the validator before invoking InitFromPSV0
, since an incorrect size will run that off the rails in a way that might just result in failure to load (malformed), or maybe not any error depending on the data and sizes, rather than a more specific error that we could emit in the validator. If this size is wrong, there's no point in trying to read anything else.
@@ -1078,6 +1086,8 @@ DxilPipelineStateValidation::ReadOrWrite(const void *pBits, uint32_t *pSize, | |||
if (mode == RWMode::CalcSize) { | |||
*pSize = rw.GetSize(); | |||
m_pPSVRuntimeInfo1 = nullptr; // clear ptr to tempRuntimeInfo | |||
} else if (mode == RWMode::Read) { | |||
*pSize = rw.GetOffset(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should not need this, as the passed in size when reading is the size of the container part.
CalcSize mode should be sufficient for validating the container part size is that calculated size, aligned, and any padding for alignment is filled with zero.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reverting this eliminates the need to add GetOffset()
as well.
DxilPipelineStateValidation PSV; | ||
if (!PSV.InitFromPSV0(pPSVData, PSVSize)) { | ||
uint32_t PSVSizeRead = PSVSize; | ||
if (!PSV.InitFromPSV0(pPSVData, &PSVSizeRead, PSVVersion)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before we do this, we should check that m_uPSVRuntimeInfoSize
(first 4 bytes of data) matches expected size for PSVVersion (PSVInitInfo(PSVVersion).RuntimeInfoSize()
), reporting error and aborting if not.
Then, there's no need to pass PSVVersion to InitFromPSV0
, and that overload can be removed/reverted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should also probably do some additional manual validation of sizes/strides based on the format before diving into the full InitFromPSV0
:
size_t offset = 4
- start by checking that the
offset + PSVRuntimeInfo_size <= PSVSizeRead
- map the appropriate PSV structures fitting in size manually to
pPSVData + offset
.offset += PSVRuntimeInfo_size
- if ResourceCount > 0:
- next 4 bytes is PSVResourceBindInfo_size, which must be
== PSVInitInfo(PSVVersion).ResourceBindInfoSize()
.offset += 4
- check that
offset + PSVResourceBindInfo_size * ResourceCount <= PSVSizeRead
.offset += PSVResourceBindInfo_size * ResourceCount
- next 4 bytes is PSVResourceBindInfo_size, which must be
- if
pPSVRuntimeInfo1
:offset + 4 <= PSVSizeRead
. Readuint32_t StringTableSize
.offset += 4
- if
StringTableSize
non-zero:StringTableSize
must be 4-aligned,offset + StringTableSize <= PSVSizeRead
,pPSVData + offset + StringTableSize - 1
must be null char\0
.offset += StringTableSize
offset + 4 <= PSVSizeRead
. Readuint32_t SemanticIndexTableEntries
.offset += 4
offset + (SemanticIndexTableEntries * 4) <= PSVSizeRead
.offset += SemanticIndexTableEntries * 4
- if
- If
SigInputElements || SigOutputElements || SigPatchConstOrPrimElements
:offset + 4 <= PSVSizeRead
. Readuint32_t PSVSignatureElement_size
.offset += 4
PSVSignatureElement_size == PSVInitInfo(PSVVersion).SignatureElementSize()
offset + PSVSignatureElement_size * (SigInputElements + SigOutputElements + SigPatchConstOrPrimElements) <= PSVSizeRead
After this, it's possible to manually go through the ViewID data calculation, but not much you can report other than it being malformed if the overall size isn't correct, since there are no more specific size values in this part of the data that can be independently verified. Since InitFromPSV0
will already do that, you can just move on I think.
If anything fails in here, we should not attempt to continue with more checks or load the PSV0 data.
It's a lot, I know, but doing this is necessary to prevent incorrect sizes for records, which can allow either extra unvalidated embedded data, or result in general malformed error when we could be more helpful.
Note how the ReadOrWrite
method uses the CheckedReaderWriter
to ease all these calculations and checks. It would probably be best to write some helper methods similar to that, but with proper error reporting for the validator. For instance, it's useful to have a method that checks the remaining size before loading some value from the current offset, then increments the offset by the size of that value. You could simply use the CheckedReaderWriter, but there will be issues with the way errors are handled that might make it hard to report the appropriate error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Working on this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In addition to the comments, we should be checking the string and index tables more thoroughly:
- check that string table is well formed - size is aligned, buffer ends with
\0
- check that accesses to string table entries are in-bounds, before using the value.
- check that accesses to index table entries are in-bounds, and that the (index+1)+size is within dword size of index table, before using the index array.
Then use PSVInitInfo to calculate correct size of PSV.
✅ With the latest revision this PR passed the C/C++ code formatter. |
Check size of PSV part matches the PSVVersion.
Updated DxilPipelineStateValidation::ReadOrWrite to read based on initInfo.PSVVersion.
And return fail when size mismatch in RWMode::Read.
Fixes #6817