diff options
| author | Marta Rybczynska <rybczynska@gmail.com> | 2023-01-03 15:03:59 +0100 |
|---|---|---|
| committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2023-01-26 23:37:05 +0000 |
| commit | 876ff613887181d0665a5c4ba809a3d9c9ae19a5 (patch) | |
| tree | 0c6c95384fb7d70f179a51f33b79848fa12b7201 | |
| parent | bba70ce34115151362bfdc49a545ee708eb297ca (diff) | |
| download | poky-876ff613887181d0665a5c4ba809a3d9c9ae19a5.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: 878988a67b488a01f53658bcc528b5d0422672ae)
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 9b9dbbd75f..079f062f79 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 | # Timeout for blocking socket operations, such as the connection attempt. | 21 | # Timeout for blocking socket operations, such as the connection attempt. |
| 22 | CVE_SOCKET_TIMEOUT ?= "60" | 22 | CVE_SOCKET_TIMEOUT ?= "60" |
| 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, 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 |
| @@ -68,9 +60,60 @@ python do_fetch() { | |||
| 68 | pass | 60 | pass |
| 69 | 61 | ||
| 70 | bb.utils.mkdirhier(db_dir) | 62 | bb.utils.mkdirhier(db_dir) |
| 63 | if os.path.exists(db_file): | ||
| 64 | shutil.copy2(db_file, db_tmp_file) | ||
| 65 | |||
| 66 | if update_db_file(db_tmp_file, d) == True: | ||
| 67 | # Update downloaded correctly, can swap files | ||
| 68 | shutil.move(db_tmp_file, db_file) | ||
| 69 | else: | ||
| 70 | # Update failed, do not modify the database | ||
| 71 | bb.note("CVE database update failed") | ||
| 72 | os.remove(db_tmp_file) | ||
| 73 | } | ||
| 74 | |||
| 75 | do_fetch[lockfiles] += "${CVE_CHECK_DB_FILE_LOCK}" | ||
| 76 | do_fetch[file-checksums] = "" | ||
| 77 | do_fetch[vardeps] = "" | ||
| 78 | |||
| 79 | def cleanup_db_download(db_file, db_tmp_file): | ||
| 80 | """ | ||
| 81 | Cleanup the download space from possible failed downloads | ||
| 82 | """ | ||
| 83 | |||
| 84 | # Clean up the updates done on the main file | ||
| 85 | # Remove it only if a journal file exists - it means a complete re-download | ||
| 86 | if os.path.exists("{0}-journal".format(db_file)): | ||
| 87 | # If a journal is present the last update might have been interrupted. In that case, | ||
| 88 | # just wipe any leftovers and force the DB to be recreated. | ||
| 89 | os.remove("{0}-journal".format(db_file)) | ||
| 90 | |||
| 91 | if os.path.exists(db_file): | ||
| 92 | os.remove(db_file) | ||
| 93 | |||
| 94 | # Clean-up the temporary file downloads, we can remove both journal | ||
| 95 | # and the temporary database | ||
| 96 | if os.path.exists("{0}-journal".format(db_tmp_file)): | ||
| 97 | # If a journal is present the last update might have been interrupted. In that case, | ||
| 98 | # just wipe any leftovers and force the DB to be recreated. | ||
| 99 | os.remove("{0}-journal".format(db_tmp_file)) | ||
| 100 | |||
| 101 | if os.path.exists(db_tmp_file): | ||
| 102 | os.remove(db_tmp_file) | ||
| 103 | |||
| 104 | def update_db_file(db_tmp_file, d): | ||
| 105 | """ | ||
| 106 | Update the given database file | ||
| 107 | """ | ||
| 108 | import bb.utils, bb.progress | ||
| 109 | from datetime import date | ||
| 110 | import urllib, gzip, sqlite3 | ||
| 111 | |||
| 112 | YEAR_START = 2002 | ||
| 113 | cve_socket_timeout = int(d.getVar("CVE_SOCKET_TIMEOUT")) | ||
| 71 | 114 | ||
| 72 | # Connect to database | 115 | # Connect to database |
| 73 | conn = sqlite3.connect(db_file) | 116 | conn = sqlite3.connect(db_tmp_file) |
| 74 | initialize_db(conn) | 117 | initialize_db(conn) |
| 75 | 118 | ||
| 76 | with bb.progress.ProgressHandler(d) as ph, open(os.path.join(d.getVar("TMPDIR"), 'cve_check'), 'a') as cve_f: | 119 | with bb.progress.ProgressHandler(d) as ph, open(os.path.join(d.getVar("TMPDIR"), 'cve_check'), 'a') as cve_f: |
| @@ -88,7 +131,7 @@ python do_fetch() { | |||
| 88 | except urllib.error.URLError as e: | 131 | except urllib.error.URLError as e: |
| 89 | cve_f.write('Warning: CVE db update error, Unable to fetch CVE data.\n\n') | 132 | cve_f.write('Warning: CVE db update error, Unable to fetch CVE data.\n\n') |
| 90 | bb.warn("Failed to fetch CVE data (%s)" % e.reason) | 133 | bb.warn("Failed to fetch CVE data (%s)" % e.reason) |
| 91 | return | 134 | return False |
| 92 | 135 | ||
| 93 | if response: | 136 | if response: |
| 94 | for l in response.read().decode("utf-8").splitlines(): | 137 | for l in response.read().decode("utf-8").splitlines(): |
| @@ -98,7 +141,7 @@ python do_fetch() { | |||
| 98 | break | 141 | break |
| 99 | else: | 142 | else: |
| 100 | bb.warn("Cannot parse CVE metadata, update failed") | 143 | bb.warn("Cannot parse CVE metadata, update failed") |
| 101 | return | 144 | return False |
| 102 | 145 | ||
| 103 | # Compare with current db last modified date | 146 | # Compare with current db last modified date |
| 104 | cursor = conn.execute("select DATE from META where YEAR = ?", (year,)) | 147 | cursor = conn.execute("select DATE from META where YEAR = ?", (year,)) |
| @@ -119,7 +162,7 @@ python do_fetch() { | |||
| 119 | except urllib.error.URLError as e: | 162 | except urllib.error.URLError as e: |
| 120 | cve_f.write('Warning: CVE db update error, CVE data is outdated.\n\n') | 163 | cve_f.write('Warning: CVE db update error, CVE data is outdated.\n\n') |
| 121 | bb.warn("Cannot parse CVE data (%s), update failed" % e.reason) | 164 | bb.warn("Cannot parse CVE data (%s), update failed" % e.reason) |
| 122 | return | 165 | return False |
| 123 | else: | 166 | else: |
| 124 | bb.debug(2, "Already up to date (last modified %s)" % last_modified) | 167 | bb.debug(2, "Already up to date (last modified %s)" % last_modified) |
| 125 | # Update success, set the date to cve_check file. | 168 | # Update success, set the date to cve_check file. |
| @@ -128,11 +171,7 @@ python do_fetch() { | |||
| 128 | 171 | ||
| 129 | conn.commit() | 172 | conn.commit() |
| 130 | conn.close() | 173 | conn.close() |
| 131 | } | 174 | return True |
| 132 | |||
| 133 | do_fetch[lockfiles] += "${CVE_CHECK_DB_FILE_LOCK}" | ||
| 134 | do_fetch[file-checksums] = "" | ||
| 135 | do_fetch[vardeps] = "" | ||
| 136 | 175 | ||
| 137 | def initialize_db(conn): | 176 | def initialize_db(conn): |
| 138 | with conn: | 177 | with conn: |
