Skip to content

Commit e4fb057

Browse files
authored
fix: allow ../ symlink Streams when they're still within the package (#363)
* fix: allow symlinks that point to higher directories but still validate that they are within the filesystem * add invalid symlink to test readstream failure on outside package * add additional test for bad symlink fixture
1 parent dd7fb18 commit e4fb057

File tree

9 files changed

+132
-30
lines changed

9 files changed

+132
-30
lines changed

src/asar.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,13 @@ export async function createPackageFromStreams(dest: string, streams: AsarStream
284284
mode: stream.stat.mode,
285285
unpack: stream.unpacked,
286286
});
287-
filesystem.insertLink(filename, stream.unpacked, stream.symlink);
287+
filesystem.insertLink(
288+
filename,
289+
stream.unpacked,
290+
path.dirname(filename),
291+
stream.symlink,
292+
src,
293+
);
288294
break;
289295
}
290296
return Promise.resolve();

src/filesystem.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,14 @@ export class Filesystem {
156156
this.offset += BigInt(size);
157157
}
158158

159-
insertLink(p: string, shouldUnpack: boolean, link: string = this.resolveLink(p)) {
159+
insertLink(
160+
p: string,
161+
shouldUnpack: boolean,
162+
parentPath: string = fs.realpathSync(path.dirname(p)),
163+
symlink: string = fs.readlinkSync(p), // /var/tmp => /private/var
164+
src: string = fs.realpathSync(this.src),
165+
) {
166+
const link = this.resolveLink(src, parentPath, symlink);
160167
if (link.startsWith('..')) {
161168
throw new Error(`${p}: file "${link}" links out of the package`);
162169
}
@@ -169,11 +176,9 @@ export class Filesystem {
169176
return link;
170177
}
171178

172-
private resolveLink(p: string) {
173-
const symlink = fs.readlinkSync(p);
174-
// /var/tmp => /private/var
175-
const parentPath = fs.realpathSync(path.dirname(p));
176-
const link = path.relative(fs.realpathSync(this.src), path.join(parentPath, symlink));
179+
private resolveLink(src: string, parentPath: string, symlink: string) {
180+
const target = path.join(parentPath, symlink);
181+
const link = path.relative(src, target);
177182
return link;
178183
}
179184

test/__snapshots__/api-spec.js.snap

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,54 @@ Array [
497497
]
498498
`;
499499

500+
exports[`api should create package from array of NodeJS.ReadableStreams with valid symlinks 1`] = `
501+
Object {
502+
"files": Object {
503+
"A": Object {
504+
"files": Object {
505+
"real.txt": Object {
506+
"integrity": Object {
507+
"algorithm": "SHA256",
508+
"blockSize": 4194304,
509+
"blocks": Array [
510+
"9ef260904c38a0173137ba5d9e95d1739b8dcac205847f202f6cff418a989bbe",
511+
],
512+
"hash": "9ef260904c38a0173137ba5d9e95d1739b8dcac205847f202f6cff418a989bbe",
513+
},
514+
"offset": "0",
515+
"size": 19,
516+
},
517+
"reverse-symlink.txt": Object {
518+
"link": "B/reverse-symlink.txt",
519+
},
520+
},
521+
},
522+
"B": Object {
523+
"files": Object {
524+
"reverse-symlink.txt": Object {
525+
"integrity": Object {
526+
"algorithm": "SHA256",
527+
"blockSize": 4194304,
528+
"blocks": Array [
529+
"766653f7a7a3e98a498888d5b7784bf808c5305bf5a01953ca866f7f36a9e111",
530+
],
531+
"hash": "766653f7a7a3e98a498888d5b7784bf808c5305bf5a01953ca866f7f36a9e111",
532+
},
533+
"offset": "19",
534+
"size": 22,
535+
},
536+
},
537+
},
538+
"Current": Object {
539+
"link": "A",
540+
},
541+
"real.txt": Object {
542+
"link": "Current/real.txt",
543+
},
544+
},
545+
}
546+
`;
547+
500548
exports[`api should handle multibyte characters in paths 1`] = `
501549
Object {
502550
"files": Object {

test/api-spec.js

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ const { compFiles, isSymbolicLinkSync } = require('./util/compareFiles');
1212
const transform = require('./util/transformStream');
1313
const { TEST_APPS_DIR } = require('./util/constants');
1414
const { verifySmartUnpack } = require('./util/verifySmartUnpack');
15-
const { crawl, determineFileType } = require('../lib/crawlfs');
16-
const path = require('path');
17-
const walk = require('./util/walk');
15+
const createReadStreams = require('./util/createReadstreams');
1816

1917
async function assertPackageListEquals(actualList, expectedFilename) {
2018
const expected = await fs.readFile(expectedFilename, 'utf8');
@@ -155,6 +153,13 @@ describe('api', function () {
155153
asar.extractAll('test/input/bad-symlink.asar', 'tmp/bad-symlink/');
156154
});
157155
});
156+
it('should throw when packaging symlink outside package', async function () {
157+
const src = 'test/input/packthis-with-bad-symlink/';
158+
const out = 'tmp/packthis-read-stream-bad-symlink.asar';
159+
assert.rejects(async () => {
160+
await asar.createPackage(src, out);
161+
});
162+
});
158163
}
159164
it('should handle multibyte characters in paths', async () => {
160165
await asar.createPackageWithOptions(
@@ -171,29 +176,38 @@ describe('api', function () {
171176
});
172177
it('should create package from array of NodeJS.ReadableStreams', async () => {
173178
const src = 'test/input/packthis-glob/';
174-
const filenames = walk(src);
179+
const streams = await createReadStreams(src);
175180

176-
const streams = await Promise.all(
177-
filenames.map(async (filename, i) => {
178-
const meta = await determineFileType(filename);
179-
return {
180-
path: path.relative(src, filename),
181-
streamGenerator:
182-
meta.type === 'directory' ? undefined : () => fs.createReadStream(filename),
183-
symlink: meta.type === 'link' ? await fs.readlink(filename) : undefined,
184-
unpacked: filename.includes('x1'),
185-
type: meta.type,
186-
stat: meta.stat,
187-
};
188-
}),
189-
);
190-
191-
await asar.createPackageFromStreams('tmp/packthis-read-stream.asar', streams);
192-
await verifySmartUnpack('tmp/packthis-read-stream.asar');
193-
await compFiles('tmp/packthis-read-stream.asar', 'test/expected/packthis-read-stream.asar');
194-
asar.extractAll('tmp/packthis-read-stream.asar', 'tmp/extractthis-read-stream/');
181+
const out = 'tmp/packthis-read-stream.asar';
182+
await asar.createPackageFromStreams(out, streams);
183+
await verifySmartUnpack(out);
184+
await compFiles(out, 'test/expected/packthis-read-stream.asar');
185+
asar.extractAll(out, 'tmp/extractthis-read-stream/');
195186
return compDirs('tmp/extractthis-read-stream/', src);
196187
});
188+
189+
it('should create package from array of NodeJS.ReadableStreams with valid symlinks', async function () {
190+
if (os.platform() === 'win32') {
191+
this.skip();
192+
}
193+
const src = 'test/input/packthis-with-symlink/';
194+
const streams = await createReadStreams(src);
195+
196+
const out = 'tmp/packthis-read-stream-symlink.asar';
197+
await asar.createPackageFromStreams(out, streams);
198+
await verifySmartUnpack(out);
199+
await compFiles(out, 'test/expected/packthis-read-stream-symlink.asar');
200+
asar.extractAll(out, 'tmp/extractthis-read-stream-symlink/');
201+
return compDirs('tmp/extractthis-read-stream-symlink/', src);
202+
});
203+
it('should throw when using NodeJS.ReadableStreams with symlink outside package', async function () {
204+
const src = 'test/input/packthis-with-bad-symlink/';
205+
const streams = await createReadStreams(src);
206+
207+
assert.rejects(async () => {
208+
await asar.createPackageFromStreams(out, streams);
209+
});
210+
});
197211
it('should extract a text file from archive with multibyte characters in path', async () => {
198212
const actual = asar
199213
.extractFile('test/expected/packthis-unicode-path.asar', 'dir1/女の子.txt')
717 Bytes
Binary file not shown.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../reverse-symlink.txt
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../B/reverse-symlink.txt
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
I SYMLINK TO SUPER DIR

test/util/createReadstreams.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const { determineFileType } = require('../../lib/crawlfs');
2+
const walk = require('./walk');
3+
const path = require('path');
4+
const fs = require('../../lib/wrapped-fs').default;
5+
6+
const collectReadStreams = async (src) => {
7+
const filenames = walk(src);
8+
9+
const streams = await Promise.all(
10+
filenames.map(async (filename) => {
11+
const meta = await determineFileType(filename);
12+
return {
13+
path: path.relative(src, filename),
14+
streamGenerator:
15+
meta.type === 'directory' ? undefined : () => fs.createReadStream(filename),
16+
symlink: meta.type === 'link' ? await fs.readlink(filename) : undefined,
17+
unpacked: filename.includes('x1'),
18+
type: meta.type,
19+
stat: meta.stat,
20+
};
21+
}),
22+
);
23+
return streams;
24+
};
25+
26+
module.exports = collectReadStreams;

0 commit comments

Comments
 (0)