Skip to content

Commit 1abf25c

Browse files
uurienwatson
authored andcommitted
Fix fs and rasp/iast problems (#6088)
1 parent c829318 commit 1abf25c

File tree

7 files changed

+388
-293
lines changed

7 files changed

+388
-293
lines changed

packages/dd-trace/src/appsec/rasp/fs-plugin.js

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,31 @@ const enabledFor = {
1414

1515
let fsPlugin
1616

17-
function enterWith (fsProps, store = storage('legacy').getStore()) {
17+
function getStoreToStart (fsProps, store = storage('legacy').getStore()) {
1818
if (store && !store.fs?.opExcluded) {
19-
storage('legacy').enterWith({
19+
return {
2020
...store,
2121
fs: {
2222
...store.fs,
2323
...fsProps,
2424
parentStore: store
2525
}
26-
})
26+
}
2727
}
28+
29+
return store
2830
}
2931

3032
class AppsecFsPlugin extends Plugin {
3133
enable () {
32-
this.addSub('apm:fs:operation:start', this._onFsOperationStart)
33-
this.addSub('apm:fs:operation:finish', this._onFsOperationFinishOrRenderEnd)
34-
this.addSub('tracing:datadog:express:response:render:start', this._onResponseRenderStart)
35-
this.addSub('tracing:datadog:express:response:render:end', this._onFsOperationFinishOrRenderEnd)
34+
this.addBind('apm:fs:operation:start', this._onFsOperationStart)
35+
this.addBind('apm:fs:operation:finish', this._onFsOperationFinishOrRenderEnd)
36+
this.addBind('tracing:datadog:express:response:render:start', this._onResponseRenderStart)
37+
this.addBind('tracing:datadog:express:response:render:end', this._onFsOperationFinishOrRenderEnd)
38+
// TODO Remove this when dc-polyfill is fixed&updated
39+
// hack to node 18 and early 20.x
40+
// with dc-polyfill addBind is not enough to force a channel.hasSubscribers === true
41+
this.addSub('tracing:datadog:express:response:render:start', () => {})
3642

3743
super.configure(true)
3844
}
@@ -44,19 +50,20 @@ class AppsecFsPlugin extends Plugin {
4450
_onFsOperationStart () {
4551
const store = storage('legacy').getStore()
4652
if (store) {
47-
enterWith({ root: store.fs?.root === undefined }, store)
53+
return getStoreToStart({ root: store.fs?.root === undefined }, store)
4854
}
4955
}
5056

5157
_onResponseRenderStart () {
52-
enterWith({ opExcluded: true })
58+
return getStoreToStart({ opExcluded: true })
5359
}
5460

5561
_onFsOperationFinishOrRenderEnd () {
5662
const store = storage('legacy').getStore()
57-
if (store?.fs?.parentStore) {
58-
storage('legacy').enterWith(store.fs.parentStore)
63+
if (store?.fs) {
64+
return store.fs.parentStore
5965
}
66+
return store
6067
}
6168
}
6269

packages/dd-trace/test/appsec/iast/analyzers/path-traversal-analyzer.spec.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,26 @@ prepareTestServerForIast('integration test', (testThatRequestHasVulnerability, t
477477

478478
describe('test stat', () => {
479479
runFsMethodTestThreeWay('stat', 0, null, __filename)
480+
481+
describe('with two calls to async method without waiting to the callback', () => {
482+
const fsAsyncWayMethodPath = path.join(os.tmpdir(), 'fs-async-way-method.js')
483+
484+
before(() => {
485+
fs.copyFileSync(path.join(__dirname, 'resources', 'fs-async-way-method.js'), fsAsyncWayMethodPath)
486+
})
487+
488+
after(() => {
489+
fs.unlinkSync(fsAsyncWayMethodPath)
490+
})
491+
492+
testThatRequestHasVulnerability(function () {
493+
const store = storage('legacy').getStore()
494+
const iastCtx = iastContextFunctions.getIastContext(store)
495+
const callArgs = [fsAsyncWayMethodPath]
496+
callArgs[0] = newTaintedString(iastCtx, callArgs[0], 'param', 'Request')
497+
return require(fsAsyncWayMethodPath).doubleCallIgnoringCb('stat', callArgs)
498+
}, 'PATH_TRAVERSAL', { occurrences: 2 })
499+
})
480500
})
481501

482502
describe('test symlink', () => {

packages/dd-trace/test/appsec/iast/analyzers/resources/fs-async-way-method.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,21 @@
22

33
const fs = require('fs')
44

5-
module.exports = function (methodName, args, cb) {
5+
function main (methodName, args, cb) {
66
return new Promise((resolve, reject) => {
77
fs[methodName](...args, (err, res) => {
88
resolve(cb(res))
99
})
1010
})
1111
}
12+
13+
main.doubleCallIgnoringCb = function (methodName, args) {
14+
return new Promise((resolve) => {
15+
fs[methodName](...args, () => {})
16+
fs[methodName](...args, () => {
17+
resolve()
18+
})
19+
})
20+
}
21+
22+
module.exports = main

packages/dd-trace/test/appsec/rasp/fs-plugin.spec.js

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ describe('AppsecFsPlugin', () => {
2727
beforeEach(() => {
2828
configure = sinon.stub()
2929
class PluginClass {
30+
addBind (channelName, handler) {}
3031
addSub (channelName, handler) {}
3132

3233
configure (config) {
@@ -93,20 +94,18 @@ describe('AppsecFsPlugin', () => {
9394
})
9495

9596
describe('_onFsOperationStart', () => {
96-
it('should mark fs root', () => {
97+
it('should return fs root', () => {
9798
const origStore = {}
9899
storage('legacy').enterWith(origStore)
99100

100-
appsecFsPlugin._onFsOperationStart()
101+
let store = appsecFsPlugin._onFsOperationStart()
101102

102-
let store = storage('legacy').getStore()
103103
assert.property(store, 'fs')
104104
assert.propertyVal(store.fs, 'parentStore', origStore)
105105
assert.propertyVal(store.fs, 'root', true)
106106

107-
appsecFsPlugin._onFsOperationFinishOrRenderEnd()
107+
store = appsecFsPlugin._onFsOperationFinishOrRenderEnd()
108108

109-
store = storage('legacy').getStore()
110109
assert.equal(store, origStore)
111110
assert.notProperty(store, 'fs')
112111
})
@@ -115,28 +114,30 @@ describe('AppsecFsPlugin', () => {
115114
const origStore = { orig: true }
116115
storage('legacy').enterWith(origStore)
117116

118-
appsecFsPlugin._onFsOperationStart()
117+
const rootStore = appsecFsPlugin._onFsOperationStart()
119118

120-
const rootStore = storage('legacy').getStore()
121119
assert.property(rootStore, 'fs')
122120
assert.propertyVal(rootStore.fs, 'parentStore', origStore)
123121
assert.propertyVal(rootStore.fs, 'root', true)
124122

125-
appsecFsPlugin._onFsOperationStart()
123+
storage('legacy').enterWith(rootStore)
124+
125+
let store = appsecFsPlugin._onFsOperationStart()
126126

127-
let store = storage('legacy').getStore()
128127
assert.property(store, 'fs')
129128
assert.propertyVal(store.fs, 'parentStore', rootStore)
130129
assert.propertyVal(store.fs, 'root', false)
131130
assert.propertyVal(store, 'orig', true)
132131

133-
appsecFsPlugin._onFsOperationFinishOrRenderEnd()
132+
storage('legacy').enterWith(store)
133+
134+
store = appsecFsPlugin._onFsOperationFinishOrRenderEnd()
134135

135-
store = storage('legacy').getStore()
136136
assert.equal(store, rootStore)
137137

138-
appsecFsPlugin._onFsOperationFinishOrRenderEnd()
139-
store = storage('legacy').getStore()
138+
storage('legacy').enterWith(store)
139+
140+
store = appsecFsPlugin._onFsOperationFinishOrRenderEnd()
140141
assert.equal(store, origStore)
141142
})
142143
})
@@ -148,16 +149,16 @@ describe('AppsecFsPlugin', () => {
148149
const origStore = {}
149150
storage('legacy').enterWith(origStore)
150151

151-
appsecFsPlugin._onResponseRenderStart()
152+
let store = appsecFsPlugin._onResponseRenderStart()
152153

153-
let store = storage('legacy').getStore()
154154
assert.property(store, 'fs')
155155
assert.propertyVal(store.fs, 'parentStore', origStore)
156156
assert.propertyVal(store.fs, 'opExcluded', true)
157157

158-
appsecFsPlugin._onFsOperationFinishOrRenderEnd()
158+
storage('legacy').enterWith(store)
159+
160+
store = appsecFsPlugin._onFsOperationFinishOrRenderEnd()
159161

160-
store = storage('legacy').getStore()
161162
assert.equal(store, origStore)
162163
assert.notProperty(store, 'fs')
163164
})
@@ -225,6 +226,12 @@ describe('AppsecFsPlugin', () => {
225226

226227
it('should clean up store when finishing op', () => {
227228
let count = 4
229+
// TODO Remove this when node 18 is unsupported or dc-polyfill is fixed&updated
230+
// hack to node 18 and early 20.x
231+
// with dc-polyfill addBind is not enough to force a channel.hasSubscribers === true
232+
const onStart = () => {}
233+
opStartCh.subscribe(onStart)
234+
228235
const onFinish = () => {
229236
const store = storage('legacy').getStore()
230237
count--
@@ -244,6 +251,7 @@ describe('AppsecFsPlugin', () => {
244251
assert.strictEqual(count, 0)
245252
} finally {
246253
opFinishCh.unsubscribe(onFinish)
254+
opStartCh.unsubscribe(onStart)
247255
}
248256
})
249257
})

0 commit comments

Comments
 (0)