Add sha256 hash to NuGet symbols
What does this MR do and why?
In the efforts to support the symbol server capability for the NuGet Repository symbol packages, we are now ready to serve the debugging files .pdb
to the consuming debuggers such as Visual Studio. How do those debuggers consume the symbol debugging files?
Aside from all the debugger configuration details, what does matter here is the request the debugger sends to the symbol server. The request doesn't have authentication credentials, however, it includes the needed parameters that we can use to make sure the request is legitimate.
The debugger request has this form: <symbol_server_url>/:file_name/:signature/:file_name
By using the :file_name
and :signature
params, we can serve the right .pdb
file. The signature can be considered as a kind of authentication token since it cannot be known unless the debugger has the executable of the Nuget package .dll
. The signature is hashed inside this executable .dll
and in the debugging file .pdb
.
In NuGet Repository, we index the .pdb
files by storing their names and signatures. So we can receive the request from the debugger, and then look into the packages_nuget_symbols
table to find the matching record using the :file_name
and :signature
.
However, this is not good enough for security purposes. The .pdb
file might have the matching filename
and signature
, but what if the file itself was tampered with or changed in any harmful way? In this case, the debugger will download a malicious file and we will serve it as well. A lose-lose situation.
To mitigate this threat and make sure the debugger pulls the intended correct file, the request coming from the debugger (Visual Studio for example) usually includes a header named Symbolchecksum
. This header holds the sha256
hash of the requested file. This hash is stored in the executable .dll
. So the server is responsible for matching this sha256
hash with the requested .pdb
file.
Request headers sample:
{"Host"=>"gdk.test:3000",
"Gitlab-Workhorse"=>"11-10-0cfa69752d8-74ffd66ae-ee-234461-g93a49ed8ee21",
"Gitlab-Workhorse-Proxy-Start"=>"1697667453093503000",
"Symbolchecksum"=>
"SHA256:3c9ecdd0948114da9579d3edf0dc216b58215fd9f31577f7bbfd664524efb338, SHA256:ba156c4dcfa83a873613e2b4a3cfdcd9115ed7883b862f5678b43ab48764959a, SHA256:2ca70b9ae2e5145d422f53f5dc1a97b2a9aea8de12c586aef0f66486d344aa18, SHA256:fd50dbac10193b8a0449b6ae1ec22a26a55780fa946e8e76ac1f2931ee39dfad",
"X-Forwarded-For"=>"172.16.123.1",
"X-Request-Id"=>"01HD2DV4558FQCQDNM4F1VTJFY",
"X-Sendfile-Type"=>"X-Sendfile",
"Accept-Encoding"=>"gzip",
"Version"=>"HTTP/1.1"}
So in this MR, we do two things:
- Calculate the
sha256
hash for the.pdb
files we index and store. More details on how it's calculated: https://github.com/dotnet/corefx/blob/master/src/System.Reflection.Metadata/specs/PE-COFF.md#portable-pdb-checksum - Add a new
binary
database column namedfile_sha256
to thepackages_nuget_symbols
table and save the calculated hash in it.
That would enable us to inspect the coming Symbolchecksum
header and match it with what we have, if it matches, then serve the file, otherwise, we return a 404
status code.
Screenshots or screen recordings
N/A
How to set up and validate locally
Follow the same steps mentioned here. When validating the created symbol files in step 5, the field file_sha256
should be correctly populated with the sha256 hash.
sha256
hash is the same as the one in the executable?
How to validate if our computed - We need to test with an actual open-source package from nuget.org. We can use the Autofac package.
- Download the package
.nupkg
file and the symbol.snupkg
file - Push
Autofac
package and its symbol to your local gdk NuGet Repository following the same steps mentioned here - In rails console, validate that the
Packages::Nuget::Symbol
records have been created:
Packages::Package.nuget.last.nuget_symbols
- Each
Packages::Nuget::Symbol
record hasfile_path
&file_sha256
fields. We need those to validate the correctness of our computedsha256
hash. - Unzip the
.nupkg
file you downloaded for theAutofac
package. - In the extracted folder, you should find the corresponding executable for each
Packages::Nuget::Symbol
record in the samefile_path
, with one difference: instead oflib/net6.0/Autofac.pdb
, the executable has the.dll
extension. So the path for the corresponding executable would belib/net6.0/Autofac.dll
. - In the same directory of the executable you chose to test, open an
IRB
session. - In the
IRB
, execute the following commands to extract thesha256
from the executable:
content = File.read 'Autofac.dll'
index = content.index('SHA256')
content[(index + 7)..(index + 38)].unpack('H*').last
- The return of the last line should be the
sha256
hash embedded in the executable. Compare it withfile_sha256
field for the correspondingPackages::Nuget::Symbol
record and they should be the same. - You can test with any different package (as long as it has a symbol package
.snupkg
) by repeating the same steps.
MR acceptance checklist
This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.
-
I have evaluated the MR acceptance checklist for this MR.
Related to #428847 (closed)