diff options
| author | Marta Rybczynska <rybczynska@gmail.com> | 2023-01-03 15:03:59 +0100 |
|---|---|---|
| committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2023-02-13 07:44:09 +0000 |
| commit | a4eed2134159929d699520a7445d6f70a13c2578 (patch) | |
| tree | c4d5ce21f783853e89814b9989db8a8fb4cf73a5 | |
| parent | 4d69f690821fa2256680c078d2b477e541f6a836 (diff) | |
| download | poky-a4eed2134159929d699520a7445d6f70a13c2578.tar.gz | |
cve-update-db-native: avoid incomplete updates
The database update has been done on the original file. In case of
network connection issues, temporary outage of the NVD server or
a similar situation, the function could exit with incomplete data
in the database. This patch solves the issue by performing the update
on a copy of the database. It replaces the main one only if the whole
update was successful.
See https://bugzilla.yoctoproject.org/show_bug.cgi?id=14929
Reported-by: Alberto Pianon <alberto@pianon.eu>
(From OE-Core rev: 6a219c50ee12b7fb584e2db3e4dde171903acfb7)
Signed-off-by: Marta Rybczynska <marta.rybczynska@linaro.org>
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
(cherry picked from commit 8efe99214d8b005f0ecac690ce5ba17b31758f92)
Signed-off-by: Steve Sakoman <steve@sakoman.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
| -rw-r--r-- | meta/recipes-core/meta/cve-update-db-native.bb | 83 |
1 files changed, 61 insertions, 22 deletions
diff --git a/meta/recipes-core/meta/cve-update-db-native.bb b/meta/recipes-core/meta/cve-update-db-native.bb index e267671628..28605bc13b 100644 --- a/meta/recipes-core/meta/cve-update-db-native.bb +++ b/meta/recipes-core/meta/cve-update-db-native.bb | |||
| @@ -21,6 +21,8 @@ CVE_DB_UPDATE_INTERVAL ?= "86400" | |||
| 21 | CVE_SOCKET_TIMEOUT ?= "60" | 21 | CVE_SOCKET_TIMEOUT ?= "60" |
| 22 | NVDCVE_URL ?= "https://nvd.nist.gov/feeds/json/cve/1.1/nvdcve-1.1-" | 22 | NVDCVE_URL ?= "https://nvd.nist.gov/feeds/json/cve/1.1/nvdcve-1.1-" |
| 23 | 23 | ||
| 24 | CVE_DB_TEMP_FILE ?= "${CVE_CHECK_DB_DIR}/temp_nvdcve_1.1.db" | ||
| 25 | |||
| 24 | python () { | 26 | python () { |
| 25 | if not bb.data.inherits_class("cve-check", d): | 27 | if not bb.data.inherits_class("cve-check", d): |
| 26 | raise bb.parse.SkipRecipe("Skip recipe when cve-check class is not loaded.") | 28 | raise bb.parse.SkipRecipe("Skip recipe when cve-check class is not loaded.") |
| @@ -32,25 +34,15 @@ python do_fetch() { | |||
| 32 | """ | 34 | """ |
| 33 | import bb.utils | 35 | import bb.utils |
| 34 | import bb.progress | 36 | import bb.progress |
| 35 | import sqlite3, urllib, urllib.parse, shutil, gzip | 37 | import shutil |
| 36 | from datetime import date | ||
| 37 | 38 | ||
| 38 | bb.utils.export_proxies(d) | 39 | bb.utils.export_proxies(d) |
| 39 | 40 | ||
| 40 | YEAR_START = 2002 | ||
| 41 | |||
| 42 | db_file = d.getVar("CVE_CHECK_DB_FILE") | 41 | db_file = d.getVar("CVE_CHECK_DB_FILE") |
| 43 | db_dir = os.path.dirname(db_file) | 42 | db_dir = os.path.dirname(db_file) |
| 43 | db_tmp_file = d.getVar("CVE_DB_TEMP_FILE") | ||
| 44 | 44 | ||
| 45 | cve_socket_timeout = int(d.getVar("CVE_SOCKET_TIMEOUT")) | 45 | cleanup_db_download(db_file, db_tmp_file) |
| 46 | |||
| 47 | if os.path.exists("{0}-journal".format(db_file)): | ||
| 48 | # If a journal is present the last update might have been interrupted. In that case, | ||
| 49 | # just wipe any leftovers and force the DB to be recreated. | ||
| 50 | os.remove("{0}-journal".format(db_file)) | ||
| 51 | |||
| 52 | if os.path.exists(db_file): | ||
| 53 | os.remove(db_file) | ||
| 54 | 46 | ||
| 55 | # The NVD database changes once a day, so no need to update more frequently | 47 | # The NVD database changes once a day, so no need to update more frequently |
| 56 | # Allow the user to force-update | 48 | # Allow the user to force-update |
| @@ -67,9 +59,60 @@ python do_fetch() { | |||
| 67 | pass | 59 | pass |
| 68 | 60 | ||
| 69 | bb.utils.mkdirhier(db_dir) | 61 | bb.utils.mkdirhier(db_dir) |
| 62 | if os.path.exists(db_file): | ||
| 63 | shutil.copy2(db_file, db_tmp_file) | ||
| 64 | |||
| 65 | if update_db_file(db_tmp_file, d) == True: | ||
| 66 | # Update downloaded correctly, can swap files | ||
| 67 | shutil.move(db_tmp_file, db_file) | ||
| 68 | else: | ||
| 69 | # Update failed, do not modify the database | ||
| 70 | bb.note("CVE database update failed") | ||
| 71 | os.remove(db_tmp_file) | ||
| 72 | } | ||
| 73 | |||
| 74 | do_fetch[lockfiles] += "${CVE_CHECK_DB_FILE_LOCK}" | ||
| 75 | do_fetch[file-checksums] = "" | ||
| 76 | do_fetch[vardeps] = "" | ||
| 77 | |||
| 78 | def cleanup_db_download(db_file, db_tmp_file): | ||
| 79 | """ | ||
| 80 | Cleanup the download space from possible failed downloads | ||
| 81 | """ | ||
| 82 | |||
| 83 | # Clean up the updates done on the main file | ||
| 84 | # Remove it only if a journal file exists - it means a complete re-download | ||
| 85 | if os.path.exists("{0}-journal".format(db_file)): | ||
| 86 | # If a journal is present the last update might have been interrupted. In that case, | ||
| 87 | # just wipe any leftovers and force the DB to be recreated. | ||
| 88 | os.remove("{0}-journal".format(db_file)) | ||
| 89 | |||
| 90 | if os.path.exists(db_file): | ||
| 91 | os.remove(db_file) | ||
| 92 | |||
| 93 | # Clean-up the temporary file downloads, we can remove both journal | ||
| 94 | # and the temporary database | ||
| 95 | if os.path.exists("{0}-journal".format(db_tmp_file)): | ||
| 96 | # If a journal is present the last update might have been interrupted. In that case, | ||
| 97 | # just wipe any leftovers and force the DB to be recreated. | ||
| 98 | os.remove("{0}-journal".format(db_tmp_file)) | ||
| 99 | |||
| 100 | if os.path.exists(db_tmp_file): | ||
| 101 | os.remove(db_tmp_file) | ||
| 102 | |||
| 103 | def update_db_file(db_tmp_file, d): | ||
| 104 | """ | ||
| 105 | Update the given database file | ||
| 106 | """ | ||
| 107 | import bb.utils, bb.progress | ||
| 108 | from datetime import date | ||
| 109 | import urllib, gzip, sqlite3 | ||
| 110 | |||
| 111 | YEAR_START = 2002 | ||
| 112 | cve_socket_timeout = int(d.getVar("CVE_SOCKET_TIMEOUT")) | ||
| 70 | 113 | ||
| 71 | # Connect to database | 114 | # Connect to database |
| 72 | conn = sqlite3.connect(db_file) | 115 | conn = sqlite3.connect(db_tmp_file) |
| 73 | initialize_db(conn) | 116 | initialize_db(conn) |
| 74 | 117 | ||
| 75 | with bb.progress.ProgressHandler(d) as ph, open(os.path.join(d.getVar("TMPDIR"), 'cve_check'), 'a') as cve_f: | 118 | with bb.progress.ProgressHandler(d) as ph, open(os.path.join(d.getVar("TMPDIR"), 'cve_check'), 'a') as cve_f: |
| @@ -87,7 +130,7 @@ python do_fetch() { | |||
| 87 | except urllib.error.URLError as e: | 130 | except urllib.error.URLError as e: |
| 88 | cve_f.write('Warning: CVE db update error, Unable to fetch CVE data.\n\n') | 131 | cve_f.write('Warning: CVE db update error, Unable to fetch CVE data.\n\n') |
| 89 | bb.warn("Failed to fetch CVE data (%s)" % e.reason) | 132 | bb.warn("Failed to fetch CVE data (%s)" % e.reason) |
| 90 | return | 133 | return False |
| 91 | 134 | ||
| 92 | if response: | 135 | if response: |
| 93 | for l in response.read().decode("utf-8").splitlines(): | 136 | for l in response.read().decode("utf-8").splitlines(): |
| @@ -97,7 +140,7 @@ python do_fetch() { | |||
| 97 | break | 140 | break |
| 98 | else: | 141 | else: |
| 99 | bb.warn("Cannot parse CVE metadata, update failed") | 142 | bb.warn("Cannot parse CVE metadata, update failed") |
| 100 | return | 143 | return False |
| 101 | 144 | ||
| 102 | # Compare with current db last modified date | 145 | # Compare with current db last modified date |
| 103 | cursor = conn.execute("select DATE from META where YEAR = ?", (year,)) | 146 | cursor = conn.execute("select DATE from META where YEAR = ?", (year,)) |
| @@ -118,7 +161,7 @@ python do_fetch() { | |||
| 118 | except urllib.error.URLError as e: | 161 | except urllib.error.URLError as e: |
| 119 | cve_f.write('Warning: CVE db update error, CVE data is outdated.\n\n') | 162 | cve_f.write('Warning: CVE db update error, CVE data is outdated.\n\n') |
| 120 | bb.warn("Cannot parse CVE data (%s), update failed" % e.reason) | 163 | bb.warn("Cannot parse CVE data (%s), update failed" % e.reason) |
| 121 | return | 164 | return False |
| 122 | else: | 165 | else: |
| 123 | bb.debug(2, "Already up to date (last modified %s)" % last_modified) | 166 | bb.debug(2, "Already up to date (last modified %s)" % last_modified) |
| 124 | # Update success, set the date to cve_check file. | 167 | # Update success, set the date to cve_check file. |
| @@ -127,11 +170,7 @@ python do_fetch() { | |||
| 127 | 170 | ||
| 128 | conn.commit() | 171 | conn.commit() |
| 129 | conn.close() | 172 | conn.close() |
| 130 | } | 173 | return True |
| 131 | |||
| 132 | do_fetch[lockfiles] += "${CVE_CHECK_DB_FILE_LOCK}" | ||
| 133 | do_fetch[file-checksums] = "" | ||
| 134 | do_fetch[vardeps] = "" | ||
| 135 | 174 | ||
| 136 | def initialize_db(conn): | 175 | def initialize_db(conn): |
| 137 | with conn: | 176 | with conn: |
