-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Fix Projector Plugin vulnerability #7115
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -55,7 +55,11 @@ def __init__(self, *args, **kwargs): | |
| self.server = None | ||
|
|
||
| def setUp(self): | ||
| self.log_dir = self.get_temp_dir() | ||
| self.test_dir = self.get_temp_dir() | ||
| self.log_dir = os.path.join(self.test_dir, "log_dir") | ||
| self.restricted_dir = os.path.join(self.test_dir, "restricted_dir") | ||
| tf.io.gfile.makedirs(self.log_dir) | ||
| tf.io.gfile.makedirs(self.restricted_dir) | ||
|
|
||
| def testRunsWithValidCheckpoint(self): | ||
| self._GenerateProjectorTestData() | ||
|
|
@@ -197,6 +201,93 @@ def testBookmarks(self): | |
| bookmark = self._GetJson(url) | ||
| self.assertEqual(bookmark, {"a": "b"}) | ||
|
|
||
| def testMetadataServesRelativeFileWithinLogdir(self): | ||
| self._GenerateProjectorAssetsTestData(metadata_path="metadata.tsv") | ||
| self._WriteTextFile( | ||
| os.path.join(self.log_dir, "metadata.tsv"), "label\nvalue\n" | ||
| ) | ||
| self._SetupWSGIApp() | ||
|
|
||
| response = self._Get( | ||
| "/data/plugin/projector/metadata?run=.&name=embedding" | ||
| ) | ||
| self.assertEqual(response.status_code, 200) | ||
| self.assertEqual(response.data, b"label\nvalue\n") | ||
|
|
||
| def testMetadataRejectsTraversalOutsideLogdir(self): | ||
| outside_metadata_path = os.path.join( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These tests might be clearer and less error-prone if we use hard-coded, readable values, instead of relying on logic, e.g.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated this. I rewrote the setup to use explicit Thanks! |
||
| self.restricted_dir, "outside_metadata.tsv" | ||
| ) | ||
| traversal_path = "../restricted_dir/outside_metadata.tsv" | ||
| self._WriteTextFile(outside_metadata_path, "secret\n") | ||
| self._GenerateProjectorAssetsTestData(metadata_path=traversal_path) | ||
| self._SetupWSGIApp() | ||
|
|
||
| response = self._Get( | ||
| "/data/plugin/projector/metadata?run=.&name=embedding" | ||
| ) | ||
| self._AssertOutsideConfigDirResponse(response) | ||
|
|
||
| def testMetadataRejectsSymlinkOutsideLogdir(self): | ||
| outside_metadata_path = os.path.join( | ||
| self.restricted_dir, "outside_metadata.tsv" | ||
| ) | ||
| symlink_path = os.path.join(self.log_dir, "metadata-link.tsv") | ||
| self._WriteTextFile(outside_metadata_path, "secret\n") | ||
| try: | ||
| os.symlink(outside_metadata_path, symlink_path) | ||
| except (AttributeError, NotImplementedError, OSError) as e: | ||
| self.skipTest("symlinks unavailable: %s" % e) | ||
| self._GenerateProjectorAssetsTestData(metadata_path="metadata-link.tsv") | ||
| self._SetupWSGIApp() | ||
|
|
||
| response = self._Get( | ||
| "/data/plugin/projector/metadata?run=.&name=embedding" | ||
| ) | ||
| self._AssertOutsideConfigDirResponse(response) | ||
|
|
||
| def testTensorRejectsAbsolutePathOutsideLogdir(self): | ||
| outside_tensor_path = os.path.join( | ||
| self.restricted_dir, "outside_tensor.tsv" | ||
| ) | ||
| self._WriteTextFile(outside_tensor_path, "1.0\t2.0\n") | ||
| self._GenerateProjectorAssetsTestData(tensor_path=outside_tensor_path) | ||
| self._SetupWSGIApp() | ||
|
|
||
| response = self._Get( | ||
| "/data/plugin/projector/tensor?run=.&name=embedding" | ||
| ) | ||
| self._AssertOutsideConfigDirResponse(response) | ||
|
|
||
| def testBookmarksRejectAbsolutePathOutsideLogdir(self): | ||
| outside_bookmarks_path = os.path.join( | ||
| self.restricted_dir, "outside_bookmarks.json" | ||
| ) | ||
| self._WriteTextFile(outside_bookmarks_path, '{"label": "secret"}') | ||
| self._GenerateProjectorAssetsTestData( | ||
| bookmarks_path=outside_bookmarks_path | ||
| ) | ||
| self._SetupWSGIApp() | ||
|
|
||
| response = self._Get( | ||
| "/data/plugin/projector/bookmarks?run=.&name=embedding" | ||
| ) | ||
| self._AssertOutsideConfigDirResponse(response) | ||
|
|
||
| def testSpriteImageRejectsTraversalOutsideLogdir(self): | ||
| outside_sprite_path = os.path.join( | ||
| self.restricted_dir, "outside_sprite.png" | ||
| ) | ||
| traversal_path = "../restricted_dir/outside_sprite.png" | ||
| self._WriteTextFile(outside_sprite_path, "not-an-image") | ||
| self._GenerateProjectorAssetsTestData(sprite_image_path=traversal_path) | ||
| self._SetupWSGIApp() | ||
|
|
||
| response = self._Get( | ||
| "/data/plugin/projector/sprite_image?run=.&name=embedding" | ||
| ) | ||
| self._AssertOutsideConfigDirResponse(response) | ||
|
|
||
| def testEndpointsNoAssets(self): | ||
| g = tf.Graph() | ||
|
|
||
|
|
@@ -213,6 +304,10 @@ def _AssertTensorResponse(self, tensor_bytes, expected_tensor): | |
| ) | ||
| self.assertTrue(np.array_equal(tensor, expected_tensor)) | ||
|
|
||
| def _AssertOutsideConfigDirResponse(self, response): | ||
| self.assertEqual(response.status_code, 400) | ||
| self.assertIn(b"resolves outside the config directory", response.data) | ||
|
|
||
| # TODO(#2007): Cleanly separate out projector tests that require real TF | ||
| @unittest.skipUnless(USING_REAL_TF, "Test only passes when using real TF") | ||
| def testPluginIsActive(self): | ||
|
|
@@ -336,6 +431,44 @@ def _GenerateProjectorTestData(self): | |
| ) | ||
| saver.save(sess, checkpoint_path) | ||
|
|
||
| def _GenerateProjectorAssetsTestData( | ||
| self, | ||
| tensor_path="tensor.tsv", | ||
| metadata_path=None, | ||
| bookmarks_path=None, | ||
| sprite_image_path=None, | ||
| ): | ||
| self._WriteTextFile(self._ResolveAssetPath(tensor_path), "1.0\t2.0\n") | ||
|
|
||
| config = projector_config_pb2.ProjectorConfig() | ||
| embedding = config.embeddings.add() | ||
| embedding.tensor_name = "embedding" | ||
| embedding.tensor_path = tensor_path | ||
| if metadata_path is not None: | ||
| embedding.metadata_path = metadata_path | ||
| if bookmarks_path is not None: | ||
| embedding.bookmarks_path = bookmarks_path | ||
| if sprite_image_path is not None: | ||
| embedding.sprite.image_path = sprite_image_path | ||
|
|
||
| with tf.io.gfile.GFile( | ||
| os.path.join(self.log_dir, "projector_config.pbtxt"), "w" | ||
| ) as f: | ||
| f.write(text_format.MessageToString(config)) | ||
|
|
||
| def _ResolveAssetPath(self, path): | ||
| path = os.path.expanduser(path) | ||
| if os.path.isabs(path): | ||
| return os.path.realpath(path) | ||
| return os.path.realpath(os.path.join(self.log_dir, path)) | ||
|
|
||
| def _WriteTextFile(self, path, contents): | ||
| parent = os.path.dirname(path) | ||
| if parent: | ||
| tf.io.gfile.makedirs(parent) | ||
| with tf.io.gfile.GFile(path, "w") as f: | ||
| f.write(contents) | ||
|
|
||
|
|
||
| class MetadataColumnsTest(tf.test.TestCase): | ||
| def testLengthDoesNotMatch(self): | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there other possible
ValueErrors that are expected besides the one we're raising? This seems a bit odd to me. Let's just raise the error with the proper error message directly?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated this.
I changed it so we only catch
ValueErrorfromos.path.commonpath(...)itself and otherwise raise the final error message directly when the resolved path is outside the config directory.Thanks!