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

[Validator] Check size of PSV. #6924

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

python3kgae
Copy link
Contributor

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

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
Copy link
Contributor

@tex3d tex3d left a 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;
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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)
Copy link
Contributor

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 on PSVVersion in initInfo, 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();
Copy link
Contributor

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.

Copy link
Contributor

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)) {
Copy link
Contributor

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.

Copy link
Contributor

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
  • if pPSVRuntimeInfo1:
    • offset + 4 <= PSVSizeRead. Read uint32_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. Read uint32_t SemanticIndexTableEntries. offset += 4
      • offset + (SemanticIndexTableEntries * 4) <= PSVSizeRead. offset += SemanticIndexTableEntries * 4
    • If SigInputElements || SigOutputElements || SigPatchConstOrPrimElements:
      • offset + 4 <= PSVSizeRead. Read uint32_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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Working on this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

Copy link
Contributor

@tex3d tex3d left a 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.
Copy link
Contributor

github-actions bot commented Sep 19, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

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

Successfully merging this pull request may close these issues.

Allow string table in the PSV0 part to have a different order than DXC during validation
2 participants