diff --git a/src/appengine/handlers/crash_query.py b/src/appengine/handlers/crash_query.py index 7427ab644d..49a452dab6 100644 --- a/src/appengine/handlers/crash_query.py +++ b/src/appengine/handlers/crash_query.py @@ -56,10 +56,13 @@ def post(self): duplicate_testcase = data_handler.find_testcase( project, state.crash_type, state.crash_state, security_flag) - if (duplicate_testcase and duplicate_testcase.security_flag and - not access.can_user_access_testcase(duplicate_testcase)): - # Do not disclose the existence or identifiers of a security-confidential - # testcase to a user who is not allowed to access it. + if duplicate_testcase and not access.can_user_access_testcase( + duplicate_testcase): + # find_testcase only filters by project/crash signature, not by + # whether the caller has any relationship to that project. Without + # this check, any authenticated user could pass an arbitrary project + # name and learn whether a matching testcase exists there, plus its + # duplicate_id/bug_id, regardless of project ownership. duplicate_testcase = None if duplicate_testcase: result['result'] = 'duplicate' diff --git a/src/clusterfuzz/_internal/tests/appengine/handlers/crash_query_test.py b/src/clusterfuzz/_internal/tests/appengine/handlers/crash_query_test.py index e78a2f4195..b2e3c6c9e5 100644 --- a/src/clusterfuzz/_internal/tests/appengine/handlers/crash_query_test.py +++ b/src/clusterfuzz/_internal/tests/appengine/handlers/crash_query_test.py @@ -161,6 +161,37 @@ def test_duplicate_security_flag_no_access(self): 'security': True, }, response.json) + def test_duplicate_not_disclosed_without_project_access(self): + """A non-security-flagged duplicate belonging to a project the caller + has no relationship to is not disclosed either. find_testcase only + filters by project name and crash signature, so without this check any + authenticated user could learn about (and get the bug_id for) another + project's testcases just by guessing/knowing its project name and crash + signature.""" + self.mock.can_user_access_testcase.return_value = False + t = data_types.Testcase( + open=True, + status='Processed', + crash_state='Foo\nBar\nMain\n', + crash_type='Heap-buffer-overflow\nWRITE 4', + project_name='someone-elses-project', + security_flag=False, + bug_information='1337') + t.put() + + response = self.app.post_json( + '/', { + 'project': 'someone-elses-project', + 'fuzz_target': 'target', + 'stacktrace': TEST_STACKTRACE_OVERFLOW, + }) + self.assertEqual({ + 'result': 'new', + 'type': 'Heap-buffer-overflow\nWRITE 4', + 'state': 'Foo\nBar\nMain\n', + 'security': True, + }, response.json) + def test_oom(self): """Test OOM parsing.""" response = self.app.post_json(