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

fix: check uri.path is string in pathToFsPath #3751

Merged
merged 1 commit into from
Jul 12, 2021

Conversation

jsjoeio
Copy link
Contributor

@jsjoeio jsjoeio commented Jul 8, 2021

This PR adds an extra check to the pathToFsPath function to check that uri.path is indeed a string.

We do this because CodeQL believes there is currently type confusion through parameter tampering earlier up in the codebase where this is used. See security warning here.

Fixes #3750

@jsjoeio jsjoeio self-assigned this Jul 8, 2021
@jsjoeio jsjoeio added the security Security related label Jul 8, 2021
@jsjoeio jsjoeio changed the title wip: fix type confusion fix: check uri.path is string in pathToFsPath Jul 8, 2021
@codecov
Copy link

codecov bot commented Jul 8, 2021

Codecov Report

Merging #3751 (be9a687) into main (c145008) will increase coverage by 0.87%.
The diff coverage is 100.00%.

❗ Current head be9a687 differs from pull request most recent head 7ce9ee0. Consider uploading reports for the commit 7ce9ee0 to get more accurate results
Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3751      +/-   ##
==========================================
+ Coverage   62.01%   62.89%   +0.87%     
==========================================
  Files          35       35              
  Lines        1835     1838       +3     
  Branches      370      371       +1     
==========================================
+ Hits         1138     1156      +18     
+ Misses        588      577      -11     
+ Partials      109      105       -4     
Impacted Files Coverage Δ
src/node/util.ts 80.20% <100.00%> (+8.03%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c145008...7ce9ee0. Read the comment docs.

@jsjoeio jsjoeio force-pushed the jsjoeio-fix-type-confusion branch 2 times, most recently from d989df0 to 6835d2d Compare July 8, 2021 22:06
@jsjoeio jsjoeio marked this pull request as ready for review July 8, 2021 22:07
@jsjoeio jsjoeio requested a review from a team as a code owner July 8, 2021 22:07
@jsjoeio jsjoeio added the testing Anything related to testing label Jul 8, 2021
Copy link
Member

@code-asher code-asher left a comment

Choose a reason for hiding this comment

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

We may want to fix the callers instead so they aren't passing arrays to a function that expects strings.

@code-asher
Copy link
Member

Or alternatively if we want to accept arrays (to make it easier to call) we can change the type of path to be string | string[] or something like that.

@code-asher
Copy link
Member

Oh and if we allow arrays we could use getFirstString to get the first value out of the query string.

@jsjoeio
Copy link
Contributor Author

jsjoeio commented Jul 12, 2021

I'll go with your second approach: change type to accept string[] and then use the getFirstString

@jsjoeio jsjoeio marked this pull request as draft July 12, 2021 21:38
@jsjoeio jsjoeio marked this pull request as ready for review July 12, 2021 21:41
@jsjoeio jsjoeio requested a review from code-asher July 12, 2021 21:41
Copy link
Contributor

@jawnsy jawnsy left a comment

Choose a reason for hiding this comment

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

LGTM!

There's a chance this function can be called with a path that is not a string.

To catch that, we check if path is of a different type and throw an error if it
is.

This also adds a couple tests for this function.
@jsjoeio jsjoeio force-pushed the jsjoeio-fix-type-confusion branch from 92ba58b to 7ce9ee0 Compare July 12, 2021 23:39
@jsjoeio jsjoeio enabled auto-merge July 12, 2021 23:39
@jsjoeio jsjoeio merged commit a5cf018 into main Jul 12, 2021
@jsjoeio jsjoeio deleted the jsjoeio-fix-type-confusion branch July 12, 2021 23:40
Copy link
Member

@code-asher code-asher left a comment

Choose a reason for hiding this comment

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

Sorry to review after merge.

Actually I'm a bit confused because we already check if the type is a string before calling pathToFsPath. Is the checker confused because of the object? If so we probably need to extract it into a variable instead, for example:

const path = getFirstString(req.query.path)
if (path) {
  pathToFsPath(path)
}

)
})
it("should not throw an error for a string array", () => {
// @ts-expect-error We need to check other types
Copy link
Member

Choose a reason for hiding this comment

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

Can remove.

it("should throw an error if a non-string is passed in for path", () => {
expect(() =>
util
// @ts-expect-error We need to check other types
Copy link
Member

Choose a reason for hiding this comment

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

If this function can take an object we should update the types on it I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It can't.

I was just testing this logic

  if (typeof uri.path !== "string") {
    throw new Error(
      `Could not compute fsPath from given uri. Expected path to be of type string, but was of type ${typeof uri.path}.`,
    )
  }

Copy link
Member

Choose a reason for hiding this comment

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

Whoops I mean remove @ts-expect-error then update pathToFsPath

export function pathToFsPath(path: string | string[] | object, keepDriveLetterCasing = false): string

Or in other words if pathToFsPath can accept an object the types should reflect that.

Although I don't think we should make it accept a object so it probably makes more sense to remove this test. We don't currently have anywhere in the code where this arg might be an object (I think! I could be wrong.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry! I think my first comment was confusing. I added this piece of logic

  if (typeof uri.path !== "string") {
    throw new Error(
      `Could not compute fsPath from given uri. Expected path to be of type string, but was of type ${typeof uri.path}.`,
    )
  }

to make sure we handled the scenario where something other than a string was being passed in. I think there is somewhere in the codebase where we're passing in something that we typecast as a string but I'm not sure 🤔

In order to test that, I need to make path something other than a string. I chose object. And that's why I'm using
// @ts-expect-error We need to check other types.

Copy link
Member

@code-asher code-asher Jul 15, 2021

Choose a reason for hiding this comment

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

Right! What I mean is that there are two scenarios:

  1. You should be able to pass non-string arguments to pathToFsPath.
  2. You should not be able to pass non-string arguments to pathToFsPath.

If the first is true then the types in the function signature should reflect this (with any or whatever the list of possible types are). So this would entail changing the signature to something like path: any instead of path: string. Otherwise, if we can't trust our types then we'd need to add type guards in every function! (And that means testing every function with invalid arguments too---that'd be rough lol.)

If the second is true then the code that is calling pathToFsPath with invalid arguments is incorrect/broken and we will need to fix it. Usually TypeScript catches this for us but there are some cases where the data at some boundary comes in as any so we have to look out for those. I don't think that's the case here though.

In our case the data comes in as string | string[] | qs.ParsedQs | qs.ParsedQs[] | undefined. We then check that the argument is a string before calling pathToFsPath so the second option should suit our purposes:

/~https://github.com/cdr/code-server/blob/717eaa64704f69c763d6fa6502d7b1c1ba9d628f/src/node/routes/static.ts#L19-L22

/~https://github.com/cdr/code-server/blob/ddee4f748c693c26b19af964994907c7eeef6828/src/node/routes/vscode.ts#L66-L68

/~https://github.com/cdr/code-server/blob/ddee4f748c693c26b19af964994907c7eeef6828/src/node/routes/vscode.ts#L76-L78

So why do we get an error? I'm not 100% sure but I think what's happening is the tool is thinking that since req.query is an object it can be manipulated in between the if and the function call which can turn it into an invalid argument (like if someone did req.query.path = {}).

TypeScript is able to detect this and show an error but whatever tool reported this apparently can't.

If that's the case then we'll need to extract it to a constant first, e.g.:

const path = getFirstString(req.query.path)
if (path) { 
  res.set("Content-Type", getMediaMime(path)) 
  res.send(await fs.readFile(pathToFsPath(path))) 

Then our pathToFsPath can purely accept string without having to do any argument manipulation itself.

Comment on lines +466 to +470
if (typeof uri.path !== "string") {
throw new Error(
`Could not compute fsPath from given uri. Expected path to be of type string, but was of type ${typeof uri.path}.`,
)
}
Copy link
Member

Choose a reason for hiding this comment

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

getFirstString can only return a string or undefined so this could be something like:

if (!uri.path) {
  throw new Error("no path was provided")
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will do

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

Successfully merging this pull request may close these issues.

Fix type confusion through parameter tampering
3 participants