Skip to content

Commit 543b780

Browse files
committed
Fix: Use base_os_version for all build types
Ensures that the specified in is correctly applied to the image for fuzzing, coverage, and introspector builds. This resolves GLIBC/libstdc++ version mismatches that occur when projects compiled on newer OS versions are run with an older base-runner. This change modifies: - : Extracts and sets . - : Extracts and sets . - : Extracts and sets . - : Passes to for coverage and introspector steps. - : Modifies to use with . New tests have been added to: - - These tests validate that the is correctly propagated and used in the respective build steps.
1 parent 0ce0d05 commit 543b780

File tree

8 files changed

+314
-5
lines changed

8 files changed

+314
-5
lines changed

infra/build/functions/build_and_run_coverage.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ def get_build_steps( # pylint: disable=too-many-locals, too-many-arguments
125125
]
126126

127127
build_steps.append({
128-
'name': build_lib.get_runner_image_name(config.test_image_suffix),
128+
'name': build_lib.get_runner_image_name(config.test_image_suffix, config.base_image_tag),
129129
'env': coverage_env,
130130
'args': [
131131
'bash', '-c',
@@ -291,7 +291,7 @@ def get_fuzz_introspector_steps( # pylint: disable=too-many-locals, too-many-ar
291291
f'/reports/{coverage_report_latest}/linux')
292292

293293
download_coverage_steps = build_lib.download_coverage_data_steps(
294-
project.name, coverage_report_latest, bucket_name, build.out)
294+
project.name, coverage_report_latest, bucket_name, build.out, config.base_image_tag)
295295
if not download_coverage_steps:
296296
return [], f'Skipping introspector build for {project.name}. No coverage data found.'
297297
build_steps.extend(download_coverage_steps)

infra/build/functions/build_lib.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -344,16 +344,18 @@ def download_corpora_steps(project_name, test_image_suffix):
344344
return steps, None
345345

346346

347-
def download_coverage_data_steps(project_name, latest, bucket_name, out_dir):
347+
def download_coverage_data_steps(project_name, latest, bucket_name, out_dir, base_image_tag=None):
348348
"""Returns GCB steps to download coverage data for the given project"""
349349
steps = []
350350
fuzz_targets = _get_targets_list(project_name)
351351
if not fuzz_targets:
352352
sys.stderr.write('No fuzz targets found for project "%s".\n' % project_name)
353353
return None
354354

355+
runner_image_name = get_runner_image_name(None, base_image_tag)
356+
355357
steps.append({
356-
'name': 'gcr.io/oss-fuzz-base/base-runner',
358+
'name': runner_image_name,
357359
'args': ['bash', '-c', (f'mkdir -p {out_dir}/textcov_reports')]
358360
})
359361

@@ -365,7 +367,7 @@ def download_coverage_data_steps(project_name, latest, bucket_name, out_dir):
365367
'allowFailure': True
366368
})
367369
steps.append({
368-
'name': 'gcr.io/oss-fuzz-base/base-runner',
370+
'name': runner_image_name,
369371
'args': ['bash', '-c', f'ls -lrt {out_dir}/textcov_reports'],
370372
'allowFailure': True
371373
})

infra/build/functions/request_build.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ def get_build_steps(project_name, timestamp=None):
7171
project_yaml, dockerfile_lines = get_project_data(project_name)
7272
build_config = build_project.Config(
7373
build_type=build_project.FUZZING_BUILD_TYPE)
74+
75+
base_os_version = project_yaml.get('base_os_version')
76+
if base_os_version:
77+
build_config.base_image_tag = base_os_version
78+
7479
return build_project.get_build_steps(project_name,
7580
project_yaml,
7681
dockerfile_lines,

infra/build/functions/request_build_test.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@
1414
#
1515
################################################################################
1616
"""Unit tests for Cloud Function request builds which builds projects."""
17+
import base64
18+
import json
1719
import os
1820
import sys
1921
import unittest
22+
from unittest import mock
2023

2124
from google.cloud import ndb
2225

@@ -26,6 +29,8 @@
2629
import datastore_entities
2730
import request_build
2831
import test_utils
32+
import build_project
33+
import build_lib
2934

3035
# pylint: disable=no-member
3136

@@ -43,13 +48,96 @@ def setUpClass(cls):
4348
def setUp(self):
4449
test_utils.reset_ds_emulator()
4550
self.maxDiff = None # pylint: disable=invalid-name
51+
# Mocks globais para evitar chamadas de API reais
52+
self.mock_get_signed_url = mock.patch(
53+
'build_lib.get_signed_url',
54+
return_value='https://example.com/signed-url').start()
55+
self.mock_get_signed_policy = mock.patch(
56+
'build_lib.get_signed_policy_document_upload_prefix',
57+
return_value=mock.MagicMock()).start()
58+
self.mock_curl_args = mock.patch(
59+
'build_lib.signed_policy_document_curl_args', return_value=[]).start()
60+
61+
def tearDown(self):
62+
mock.patch.stopall()
4663

4764
def test_get_build_steps_no_project(self):
4865
"""Test for when project isn't available in datastore."""
4966
with ndb.Client().context():
5067
self.assertRaises(RuntimeError, request_build.get_build_steps,
5168
'test-project')
5269

70+
@mock.patch('build_project.run_build', return_value={'id': 'mock-build-id'})
71+
def test_get_build_steps_with_base_os_version(self, mock_run_build):
72+
"""Test that get_build_steps uses the base_os_version."""
73+
project_name = 'example'
74+
base_os_version = 'ubuntu-24-04'
75+
76+
project_yaml_contents = """
77+
homepage: https://my-api.example.com
78+
main_repo: https://github.com/example/my-api
79+
language: c++
80+
vendor_ccs: []
81+
fuzzing_engines:
82+
- libfuzzer
83+
sanitizers:
84+
- address
85+
base_os_version: ubuntu-24-04
86+
"""
87+
dockerfile_contents = """
88+
# Copyright 2017 Google Inc.
89+
#
90+
# Licensed under the Apache License, Version 2.0 (the "License");
91+
# you may not use this file except in compliance with the License.
92+
# You may obtain a copy of the License at
93+
#
94+
# http://www.apache.org/licenses/LICENSE-2.0
95+
#
96+
# Unless required by applicable law or agreed to in writing, software
97+
# distributed under the License is distributed on an "AS IS" BASIS,
98+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
99+
# See the License for the specific language governing permissions and
100+
# limitations under the License.
101+
#
102+
################################################################################
103+
104+
FROM gcr.io/oss-fuzz-base/base-builder
105+
RUN apt-get update && apt-get install -y make
106+
107+
# Get *your* source code here.
108+
RUN git clone https://github.com/google/oss-fuzz.git my-git-repo
109+
WORKDIR my-git-repo
110+
COPY build.sh $SRC/
111+
"""
112+
with ndb.Client().context():
113+
datastore_entities.Project(name=project_name,
114+
project_yaml_contents=project_yaml_contents,
115+
dockerfile_contents=dockerfile_contents).put()
116+
117+
event = {'data': base64.b64encode(project_name.encode('utf-8'))}
118+
119+
with mock.patch('google.auth.default', return_value=(None, 'oss-fuzz')):
120+
request_build.request_build(event, None)
121+
122+
# Check that run_build was called.
123+
self.assertTrue(mock_run_build.called)
124+
125+
# Get the build_steps from the first call to run_build.
126+
self.assertEqual(2, mock_run_build.call_count)
127+
build_steps = mock_run_build.call_args_list[0][0][1]
128+
129+
# Find the 'build-check' step and assert the runner image is correct.
130+
found_build_check_step = False
131+
for inner_step in build_steps[0]:
132+
if isinstance(
133+
inner_step,
134+
dict) and inner_step.get('id') and 'build-check' in inner_step['id']:
135+
found_build_check_step = True
136+
expected_image = f'gcr.io/oss-fuzz-base/base-runner:{base_os_version}'
137+
self.assertIn(expected_image, inner_step['args'])
138+
break
139+
self.assertTrue(found_build_check_step, 'Build check step not found.')
140+
53141
def test_build_history(self):
54142
"""Testing build history."""
55143
with ndb.Client().context():

infra/build/functions/request_coverage_build.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ def get_build_steps(project_name):
3030
build_config = request_build.get_empty_config()
3131
project_yaml_contents, dockerfile_lines = request_build.get_project_data(
3232
project_name)
33+
34+
base_os_version = project_yaml_contents.get('base_os_version')
35+
if base_os_version:
36+
build_config.base_image_tag = base_os_version
37+
3338
return build_and_run_coverage.get_build_steps(project_name,
3439
project_yaml_contents,
3540
dockerfile_lines, build_config)
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Copyright 2023 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
################################################################################
16+
"""Unit tests for Cloud Function request_coverage_build."""
17+
import base64
18+
import os
19+
import sys
20+
import unittest
21+
from unittest import mock
22+
23+
from google.cloud import ndb
24+
25+
sys.path.append(os.path.dirname(__file__))
26+
# pylint: disable=wrong-import-position
27+
28+
import datastore_entities
29+
import request_coverage_build
30+
import test_utils
31+
import yaml
32+
33+
# pylint: disable=no-member
34+
35+
36+
class TestRequestCoverageBuild(unittest.TestCase):
37+
"""Unit tests for sync."""
38+
39+
@classmethod
40+
def setUpClass(cls):
41+
cls.ds_emulator = test_utils.start_datastore_emulator()
42+
test_utils.wait_for_emulator_ready(cls.ds_emulator, 'datastore',
43+
test_utils.DATASTORE_READY_INDICATOR)
44+
test_utils.set_gcp_environment()
45+
46+
def setUp(self):
47+
test_utils.reset_ds_emulator()
48+
self.maxDiff = None
49+
50+
@mock.patch('request_build.run_build', return_value={'id': 'mock-build-id'})
51+
@mock.patch('build_lib.get_signed_url',
52+
return_value='https://mocked-signed-url.com')
53+
def test_get_build_steps_with_base_os_version(self, mock_get_signed_url,
54+
mock_run_build):
55+
"""Test that get_build_steps uses the base_os_version for coverage builds."""
56+
project_name = 'example'
57+
base_os_version = 'ubuntu-24-04'
58+
59+
project_yaml_contents = f"""
60+
homepage: https://my-api.example.com
61+
main_repo: https://github.com/example/my-api
62+
language: c++
63+
fuzzing_engines:
64+
- libfuzzer
65+
sanitizers:
66+
- address
67+
base_os_version: {base_os_version}
68+
"""
69+
dockerfile_contents = "FROM gcr.io/oss-fuzz-base/base-builder"
70+
71+
with mock.patch('request_build.get_project_data') as mock_get_project_data:
72+
mock_get_project_data.return_value = (
73+
yaml.safe_load(project_yaml_contents), dockerfile_contents)
74+
75+
event = {'data': base64.b64encode(project_name.encode('utf-8'))}
76+
77+
with mock.patch('google.auth.default', return_value=(None, 'oss-fuzz')):
78+
request_coverage_build.request_coverage_build(event, None)
79+
80+
self.assertTrue(mock_run_build.called)
81+
build_steps = mock_run_build.call_args[0][1]
82+
83+
for inner_list in build_steps:
84+
for step in inner_list:
85+
if isinstance(
86+
step, dict
87+
) and 'name' in step and 'gcr.io/oss-fuzz-base/base-runner' in step[
88+
'name']:
89+
found_build_check_step = True
90+
expected_image = f'gcr.io/oss-fuzz-base/base-runner:{base_os_version}'
91+
self.assertEqual(step['name'], expected_image)
92+
break
93+
if found_build_check_step:
94+
break
95+
self.assertTrue(found_build_check_step, 'Coverage build step not found.')
96+
97+
@classmethod
98+
def tearDownClass(cls):
99+
test_utils.cleanup_emulator(cls.ds_emulator)
100+
101+
102+
if __name__ == '__main__':
103+
unittest.main(exit=False)

infra/build/functions/request_introspector_build.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ def get_build_steps(project_name, image_project, base_images_project):
3131
build_config = request_build.get_empty_config()
3232
project_yaml_contents, dockerfile_lines = request_build.get_project_data(
3333
project_name)
34+
35+
base_os_version = project_yaml_contents.get('base_os_version')
36+
if base_os_version:
37+
build_config.base_image_tag = base_os_version
38+
3439
return build_and_run_coverage.get_fuzz_introspector_steps(
3540
project_name, project_yaml_contents, dockerfile_lines, build_config)
3641

0 commit comments

Comments
 (0)