diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 71a55d6d59187f135e1bea8399c84385cd4d0974..fe603bb3510b35566d702714d84a2ba664dbb69e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -57,8 +57,7 @@ scanbuild: CONFIG_OPTS: '-Dprofile=Devel -Dunit_tests=enabled' script: - flatpak-builder --user --disable-rofiles-fuse --stop-at=${FLATPAK_MODULE} flatpak_app ${MANIFEST_PATH} - - flatpak build flatpak_app bash -c "source /usr/lib/sdk/llvm12/enable.sh; meson --prefix=/app ${CONFIG_OPTS} _build; ninja -C _build scan-build" - - if [[ -n "$(ls -A _build/meson-logs/scanbuild/)" ]]; then echo "Scan build log found, assuming defects exist"; exit 1; fi + - flatpak build flatpak_app bash -c "source /usr/lib/sdk/llvm12/enable.sh; meson --prefix=/app ${CONFIG_OPTS} _build; SCANBUILD=$(pwd)/.run-scan-build ninja -C _build scan-build" artifacts: when: on_failure paths: diff --git a/.run-scan-build b/.run-scan-build new file mode 100755 index 0000000000000000000000000000000000000000..ea778b932a3999e7d517b068d590d5ee2319929c --- /dev/null +++ b/.run-scan-build @@ -0,0 +1,5 @@ +#!/bin/sh + +set -e + +scan-build -v --status-bugs -disable-checker unix.Malloc "$@" diff --git a/HACKING.md b/HACKING.md index 545d7c960efbf0a2aaf3a27864077ae991f4c8ec..8a050769f5a2991ebe8b73785bc008f464844cab 100644 --- a/HACKING.md +++ b/HACKING.md @@ -167,6 +167,19 @@ more `WebKitWebExtension`s (web process extensions). Meanwhile, each `WebKitWebView` will have one or more `WebKitWebPage`s. Only one page will be active in a view at a given time: the other pages are for process swaps. +# Security + +When injecting untrusted data into web content, you need to properly encode the +data for the relevant context in order to prevent XSS vulnerabilities. For +example: page titles could be malicious, URLs could be malicious, web app IDs +could be malicious, etc. You must carefully read and understand the [OWASP +XSS Prevention rules](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html) +or you will mess up. `lib/ephy-output-encoding.h` contains functions to help +with this. + +When working with JavaScript, pay particular attention to Rule #8 "Prevent DOM- +based XSS" as it is tricky and requires care throughout your JavaScript. + # Debugging To enable debugging use the configure option `-Ddeveloper_mode=true`. diff --git a/embed/ephy-about-handler.c b/embed/ephy-about-handler.c index 4c794f52605e8f824de2310e892914cdea181d4c..ee169e40a633d78270069c2a7deeb2e9760cefb9 100644 --- a/embed/ephy-about-handler.c +++ b/embed/ephy-about-handler.c @@ -27,6 +27,7 @@ #include "ephy-file-helpers.h" #include "ephy-flatpak-utils.h" #include "ephy-history-service.h" +#include "ephy-output-encoding.h" #include "ephy-prefs.h" #include "ephy-settings.h" #include "ephy-smaps.h" @@ -263,19 +264,37 @@ handle_applications_finished_cb (EphyAboutHandler *handler, for (p = applications; p; p = p->next) { EphyWebApplication *app = (EphyWebApplication *)p->data; + g_autofree char *html_encoded_id = NULL; + g_autofree char *encoded_icon_url = NULL; + g_autofree char *encoded_name = NULL; + g_autofree char *encoded_url = NULL; + g_autofree char *js_encoded_id = NULL; + g_autofree char *encoded_install_date = NULL; if (ephy_web_application_is_system (app)) continue; + /* Most of these fields are untrusted. The web app suggests its own title, + * which gets used in the app ID and icon URL. The main URL could contain + * anything. Install date is the only trusted field here in that it's + * constructed by Epiphany, but it's a freeform string and we're encoding + * everything else here anyway, so might as well encode this too. + */ + html_encoded_id = ephy_encode_for_html_attribute (app->id); + encoded_icon_url = ephy_encode_for_html_attribute (app->icon_url); + encoded_name = ephy_encode_for_html_entity (app->name); + encoded_url = ephy_encode_for_html_entity (app->url); + js_encoded_id = ephy_encode_for_javascript (app->id); + encoded_install_date = ephy_encode_for_html_entity (app->install_date); g_string_append_printf (data_str, "" "" "
%s
%s
" "" "%s
%s", - app->id, app->icon_url, app->name, app->url, _("Delete"), app->id, + html_encoded_id, encoded_icon_url, encoded_name, encoded_url, _("Delete"), js_encoded_id, /* Note for translators: this refers to the installation date. */ - _("Installed on:"), app->install_date); + _("Installed on:"), encoded_install_date); } g_string_append (data_str, ""); @@ -410,7 +429,8 @@ history_service_query_urls_cb (EphyHistoryService *history, EphyHistoryURL *url = (EphyHistoryURL *)l->data; const char *snapshot; g_autofree char *thumbnail_style = NULL; - g_autofree char *markup = NULL; + g_autofree char *encoded_title = NULL; + g_autofree char *encoded_url = NULL; snapshot = ephy_snapshot_service_lookup_cached_snapshot_path (snapshot_service, url->url); if (snapshot) @@ -418,15 +438,17 @@ history_service_query_urls_cb (EphyHistoryService *history, else ephy_embed_shell_schedule_thumbnail_update (shell, url); - markup = g_markup_escape_text (url->title, -1); + /* Title and URL are controlled by web content and could be malicious. */ + encoded_title = ephy_encode_for_html_attribute (url->title); + encoded_url = ephy_encode_for_html_attribute (url->url); g_string_append_printf (data_str, "" "
" " " " %s" "
", - markup, url->url, _("Remove from overview"), - thumbnail_style ? thumbnail_style : "", url->title); + encoded_title, encoded_url, _("Remove from overview"), + thumbnail_style ? thumbnail_style : "", encoded_title); } data_str = g_string_append (data_str, diff --git a/embed/ephy-pdf-handler.c b/embed/ephy-pdf-handler.c index 871d2487a7d0b232e814ab0ccd74d7608d329456..bcc226c5f759a95f8ad3c260a15d3f26b7e21ef2 100644 --- a/embed/ephy-pdf-handler.c +++ b/embed/ephy-pdf-handler.c @@ -23,6 +23,7 @@ #include "ephy-embed-container.h" #include "ephy-embed-shell.h" +#include "ephy-output-encoding.h" #include "ephy-web-view.h" #include @@ -121,11 +122,12 @@ pdf_file_loaded (GObject *source, gpointer user_data) { EphyPdfRequest *self = user_data; - GBytes *html_file; + g_autoptr (GBytes) html_file = NULL; g_autoptr (GError) error = NULL; g_autoptr (GString) html = NULL; - g_autofree gchar *b64 = NULL; g_autofree char *file_data = NULL; + g_autofree char *encoded_file_data = NULL; + g_autofree char *encoded_filename = NULL; gsize len = 0; if (!g_file_load_contents_finish (G_FILE (source), res, &file_data, &len, NULL, &error)) { @@ -134,13 +136,13 @@ pdf_file_loaded (GObject *source, return; } - html_file = g_resources_lookup_data ("/org/gnome/epiphany/pdfjs/web/viewer.html", 0, NULL); - - b64 = g_base64_encode ((const guchar *)file_data, len); g_file_delete_async (G_FILE (source), G_PRIORITY_DEFAULT, NULL, pdf_file_deleted, NULL); - html = g_string_new (""); - g_string_printf (html, g_bytes_get_data (html_file, NULL), b64, self->file_name ? self->file_name : ""); + html = g_string_new (NULL); + html_file = g_resources_lookup_data ("/org/gnome/epiphany/pdfjs/web/viewer.html", 0, NULL); + encoded_file_data = g_base64_encode ((const guchar *)file_data, len); + encoded_filename = self->file_name ? ephy_encode_for_html_attribute (self->file_name) : g_strdup (""); + g_string_printf (html, g_bytes_get_data (html_file, NULL), encoded_file_data, encoded_filename); finish_uri_scheme_request (self, g_strdup (html->str), NULL); } diff --git a/embed/ephy-reader-handler.c b/embed/ephy-reader-handler.c index 25adf90cbf17d7072744d76de061960d932c8cb7..0b6daa29f56eecc5b6b7cf91b2c5c9512eb981ff 100644 --- a/embed/ephy-reader-handler.c +++ b/embed/ephy-reader-handler.c @@ -24,6 +24,7 @@ #include "ephy-embed-container.h" #include "ephy-embed-shell.h" #include "ephy-lib-type-builtins.h" +#include "ephy-output-encoding.h" #include "ephy-settings.h" #include "ephy-web-view.h" @@ -123,8 +124,7 @@ enum_nick (GType enum_type, return nick; } -static -char * +static char * readability_get_property_string (WebKitJavascriptResult *js_result, char *property) { @@ -158,7 +158,10 @@ readability_js_finish_cb (GObject *object, g_autoptr (WebKitJavascriptResult) js_result = NULL; g_autoptr (GError) error = NULL; g_autofree gchar *byline = NULL; + g_autofree gchar *encoded_byline = NULL; g_autofree gchar *content = NULL; + g_autofree gchar *encoded_content = NULL; + g_autofree gchar *encoded_title = NULL; g_autoptr (GString) html = NULL; g_autoptr (GBytes) style_css = NULL; const gchar *title; @@ -176,10 +179,15 @@ readability_js_finish_cb (GObject *object, byline = readability_get_property_string (js_result, "byline"); content = readability_get_property_string (js_result, "content"); + title = webkit_web_view_get_title (web_view); + + encoded_byline = byline ? ephy_encode_for_html_entity (byline) : g_strdup (""); + encoded_content = ephy_encode_for_html_entity (content); + encoded_title = ephy_encode_for_html_entity (title); - html = g_string_new (""); + html = g_string_new (NULL); style_css = g_resources_lookup_data ("/org/gnome/epiphany/readability/reader.css", G_RESOURCE_LOOKUP_FLAGS_NONE, NULL); - title = webkit_web_view_get_title (web_view); + font_style = enum_nick (EPHY_TYPE_PREFS_READER_FONT_STYLE, g_settings_get_enum (EPHY_SETTINGS_READER, EPHY_PREFS_READER_FONT_STYLE)); @@ -206,12 +214,12 @@ readability_js_finish_cb (GObject *object, "" "
", (gchar *)g_bytes_get_data (style_css, NULL), - title, + encoded_title, font_style, color_scheme, - title, - byline != NULL ? byline : ""); - g_string_append (html, content); + encoded_title, + encoded_byline); + g_string_append (html, encoded_content); g_string_append (html, ""); finish_uri_scheme_request (request, g_strdup (html->str), NULL); diff --git a/embed/ephy-view-source-handler.c b/embed/ephy-view-source-handler.c index 115f4b718983a6f0233459c405a194c0411d447f..10e4ddc4402953a856ffe0f87e1129360a153b8f 100644 --- a/embed/ephy-view-source-handler.c +++ b/embed/ephy-view-source-handler.c @@ -23,6 +23,7 @@ #include "ephy-embed-container.h" #include "ephy-embed-shell.h" +#include "ephy-output-encoding.h" #include "ephy-web-view.h" #include @@ -109,7 +110,9 @@ web_resource_data_cb (WebKitWebResource *resource, EphyViewSourceRequest *request) { g_autofree guchar *data = NULL; - g_autofree char *escaped_str = NULL; + g_autofree char *data_str = NULL; + g_autofree char *encoded_str = NULL; + g_autofree char *encoded_uri = NULL; g_autoptr (GError) error = NULL; g_autofree char *html = NULL; gsize length; @@ -120,8 +123,13 @@ web_resource_data_cb (WebKitWebResource *resource, return; } - /* Warning: data is not a string, so we pass length here because it's not NUL-terminated. */ - escaped_str = g_markup_escape_text ((const char *)data, length); + /* Convert data to a string */ + data_str = g_malloc (length + 1); + memcpy (data_str, data, length); + data_str[length] = '\0'; + + encoded_str = ephy_encode_for_html_entity (data_str); + encoded_uri = ephy_encode_for_html_entity (webkit_web_resource_get_uri (resource)); html = g_strdup_printf ("" " " @@ -136,8 +144,8 @@ web_resource_data_cb (WebKitWebResource *resource, " hljs.initLineNumbersOnLoad();" "
%s
" "", - webkit_web_resource_get_uri (resource), - escaped_str); + encoded_uri, + encoded_str); finish_uri_scheme_request (request, g_steal_pointer (&html), NULL); } diff --git a/embed/ephy-web-view.c b/embed/ephy-web-view.c index 12cd2455da2eaa177aa3547af9c853d869566425..ae8413e94c8993b839a02a35a7836f1f9110bee6 100644 --- a/embed/ephy-web-view.c +++ b/embed/ephy-web-view.c @@ -38,6 +38,7 @@ #include "ephy-gsb-utils.h" #include "ephy-history-service.h" #include "ephy-lib-type-builtins.h" +#include "ephy-output-encoding.h" #include "ephy-permissions-manager.h" #include "ephy-prefs.h" #include "ephy-reader-handler.h" @@ -1858,9 +1859,11 @@ format_network_error_page (const char *uri, const char **icon_name, const char **style) { - char *formatted_origin; - char *formatted_reason; - char *first_paragraph; + g_autofree char *encoded_uri = NULL; + g_autofree char *encoded_origin = NULL; + g_autofree char *formatted_origin = NULL; + g_autofree char *formatted_reason = NULL; + g_autofree char *first_paragraph = NULL; const char *second_paragraph; /* Page title when a site cannot be loaded due to a network error. */ @@ -1869,7 +1872,8 @@ format_network_error_page (const char *uri, /* Message title when a site cannot be loaded due to a network error. */ *message_title = g_strdup (_("Unable to display this website")); - formatted_origin = g_strdup_printf ("%s", origin); + encoded_origin = ephy_encode_for_html_entity (origin); + formatted_origin = g_strdup_printf ("%s", encoded_origin); /* Error details when a site cannot be loaded due to a network error. */ first_paragraph = g_strdup_printf (_("The site at %s seems to be " "unavailable."), @@ -1891,16 +1895,13 @@ format_network_error_page (const char *uri, /* The button on the network error page. DO NOT ADD MNEMONICS HERE. */ *button_label = g_strdup (_("Reload")); - *button_action = g_strdup_printf ("window.location = '%s';", uri); + encoded_uri = ephy_encode_for_javascript (uri); + *button_action = g_strdup_printf ("window.location = '%s';", encoded_uri); /* Mnemonic for the Reload button on browser error pages. */ *button_accesskey = C_("reload-access-key", "R"); *icon_name = "network-error-symbolic.svg"; *style = "default"; - - g_free (formatted_origin); - g_free (formatted_reason); - g_free (first_paragraph); } static void @@ -1914,10 +1915,12 @@ format_crash_error_page (const char *uri, const char **icon_name, const char **style) { - char *formatted_uri; - char *formatted_distributor; - char *first_paragraph; - char *second_paragraph; + g_autofree char *html_encoded_uri = NULL; + g_autofree char *js_encoded_uri = NULL; + g_autofree char *formatted_uri = NULL; + g_autofree char *formatted_distributor = NULL; + g_autofree char *first_paragraph = NULL; + g_autofree char *second_paragraph = NULL; /* Page title when a site cannot be loaded due to a page crash error. */ *page_title = g_strdup_printf (_("Problem Loading Page")); @@ -1925,7 +1928,8 @@ format_crash_error_page (const char *uri, /* Message title when a site cannot be loaded due to a page crash error. */ *message_title = g_strdup (_("Oops! There may be a problem")); - formatted_uri = g_strdup_printf ("%s", uri); + html_encoded_uri = ephy_encode_for_html_entity (uri); + formatted_uri = g_strdup_printf ("%s", html_encoded_uri); /* Error details when a site cannot be loaded due to a page crash error. */ first_paragraph = g_strdup_printf (_("The page %s may have caused Web to " "close unexpectedly."), @@ -1944,17 +1948,13 @@ format_crash_error_page (const char *uri, /* The button on the page crash error page. DO NOT ADD MNEMONICS HERE. */ *button_label = g_strdup (_("Reload")); - *button_action = g_strdup_printf ("window.location = '%s';", uri); + js_encoded_uri = ephy_encode_for_javascript (uri); + *button_action = g_strdup_printf ("window.location = '%s';", js_encoded_uri); /* Mnemonic for the Reload button on browser error pages. */ *button_accesskey = C_("reload-access-key", "R"); *icon_name = "computer-fail-symbolic.svg"; *style = "default"; - - g_free (formatted_uri); - g_free (formatted_distributor); - g_free (first_paragraph); - g_free (second_paragraph); } static void @@ -1968,6 +1968,7 @@ format_process_crash_error_page (const char *uri, const char **icon_name, const char **style) { + g_autofree char *encoded_uri = NULL; const char *first_paragraph; /* Page title when a site cannot be loaded due to a process crash error. */ @@ -1983,7 +1984,8 @@ format_process_crash_error_page (const char *uri, /* The button on the process crash error page. DO NOT ADD MNEMONICS HERE. */ *button_label = g_strdup (_("Reload")); - *button_action = g_strdup_printf ("window.location = '%s';", uri); + encoded_uri = ephy_encode_for_javascript (uri); + *button_action = g_strdup_printf ("window.location = '%s';", encoded_uri); /* Mnemonic for the Reload button on browser error pages. */ *button_accesskey = C_("reload-access-key", "R"); @@ -2002,6 +2004,7 @@ format_unresponsive_process_error_page (const char *uri, const char **icon_name, const char **style) { + g_autofree char *encoded_uri = NULL; const char *first_paragraph; /* Page title when web content has become unresponsive. */ @@ -2017,7 +2020,8 @@ format_unresponsive_process_error_page (const char *uri, /* The button on the unresponsive process error page. DO NOT ADD MNEMONICS HERE. */ *button_label = g_strdup (_("Reload")); - *button_action = g_strdup_printf ("window.location = '%s';", uri); + encoded_uri = ephy_encode_for_javascript (uri); + *button_action = g_strdup_printf ("window.location = '%s';", encoded_uri); /* Mnemonic for the Reload button on browser error pages. */ *button_accesskey = C_("reload-access-key", "R"); @@ -2041,8 +2045,9 @@ format_tls_error_page (EphyWebView *view, const char **icon_name, const char **style) { - char *formatted_origin; - char *first_paragraph; + g_autofree char *encoded_origin = NULL; + g_autofree char *formatted_origin = NULL; + g_autofree char *first_paragraph = NULL; /* Page title when a site is not loaded due to an invalid TLS certificate. */ *page_title = g_strdup_printf (_("Security Violation")); @@ -2050,7 +2055,8 @@ format_tls_error_page (EphyWebView *view, /* Message title when a site is not loaded due to an invalid TLS certificate. */ *message_title = g_strdup (_("This Connection is Not Secure")); - formatted_origin = g_strdup_printf ("%s", origin); + encoded_origin = ephy_encode_for_html_entity (origin); + formatted_origin = g_strdup_printf ("%s", encoded_origin); /* Error details when a site is not loaded due to an invalid TLS certificate. */ first_paragraph = g_strdup_printf (_("This does not look like the real %s. " "Attackers might be trying to steal or " @@ -2076,9 +2082,6 @@ format_tls_error_page (EphyWebView *view, *icon_name = "channel-insecure-symbolic.svg"; *style = "danger"; - - g_free (formatted_origin); - g_free (first_paragraph); } static void @@ -2098,8 +2101,9 @@ format_unsafe_browsing_error_page (EphyWebView *view, const char **icon_name, const char **style) { - char *formatted_origin; - char *first_paragraph; + g_autofree char *encoded_origin = NULL; + g_autofree char *formatted_origin = NULL; + g_autofree char *first_paragraph = NULL; /* Page title when a site is flagged by Google Safe Browsing verification. */ *page_title = g_strdup_printf (_("Security Warning")); @@ -2107,7 +2111,8 @@ format_unsafe_browsing_error_page (EphyWebView *view, /* Message title on the unsafe browsing error page. */ *message_title = g_strdup (_("Unsafe website detected!")); - formatted_origin = g_strdup_printf ("%s", origin); + encoded_origin = ephy_encode_for_html_entity (origin); + formatted_origin = g_strdup_printf ("%s", encoded_origin); /* Error details on the unsafe browsing error page. * https://developers.google.com/safe-browsing/v4/usage-limits#UserWarnings */ @@ -2165,9 +2170,6 @@ format_unsafe_browsing_error_page (EphyWebView *view, *icon_name = "security-high-symbolic.svg"; *style = "danger"; - - g_free (formatted_origin); - g_free (first_paragraph); } static void @@ -2181,7 +2183,8 @@ format_no_such_file_error_page (EphyWebView *view, const char **icon_name, const char **style) { - g_autofree gchar *formatted_origin = NULL; + g_autofree gchar *encoded_address = NULL; + g_autofree gchar *formatted_address = NULL; g_autofree gchar *first_paragraph = NULL; g_autofree gchar *second_paragraph = NULL; @@ -2191,10 +2194,11 @@ format_no_such_file_error_page (EphyWebView *view, /* Message title on the no such file error page. */ *message_title = g_strdup (_("File not found")); - formatted_origin = g_strdup_printf ("%s", view->address); + encoded_address = ephy_encode_for_html_entity (view->address); + formatted_address = g_strdup_printf ("%s", encoded_address); first_paragraph = g_strdup_printf (_("%s could not be found."), - formatted_origin); + formatted_address); second_paragraph = g_strdup_printf (_("Please check the file name for " "capitalization or other typing errors. Also check if " "it has been moved, renamed, or deleted.")); @@ -2229,19 +2233,19 @@ ephy_web_view_load_error_page (EphyWebView *view, GError *error, gpointer user_data) { - GBytes *html_file; - GString *html = g_string_new (""); - char *origin = NULL; - char *lang = NULL; - char *page_title = NULL; - char *msg_title = NULL; - char *msg_body = NULL; - char *msg_details = NULL; - char *button_label = NULL; - char *hidden_button_label = NULL; - char *button_action = NULL; - char *hidden_button_action = NULL; - char *style_sheet = NULL; + g_autoptr (GBytes) html_file = NULL; + g_autoptr (GString) html = g_string_new (NULL); + g_autofree char *origin = NULL; + g_autofree char *lang = NULL; + g_autofree char *page_title = NULL; + g_autofree char *msg_title = NULL; + g_autofree char *msg_body = NULL; + g_autofree char *msg_details = NULL; + g_autofree char *button_label = NULL; + g_autofree char *hidden_button_label = NULL; + g_autofree char *button_action = NULL; + g_autofree char *hidden_button_action = NULL; + g_autofree char *style_sheet = NULL; const char *button_accesskey = NULL; const char *hidden_button_accesskey = NULL; const char *icon_name = NULL; @@ -2392,23 +2396,9 @@ ephy_web_view_load_error_page (EphyWebView *view, button_accesskey, button_label); #pragma GCC diagnostic pop - g_bytes_unref (html_file); - g_free (origin); - g_free (lang); - g_free (page_title); - g_free (msg_title); - g_free (msg_body); - g_free (msg_details); - g_free (button_label); - g_free (button_action); - g_free (hidden_button_label); - g_free (hidden_button_action); - g_free (style_sheet); - /* Make our history backend ignore the next page load, since it will be an error page. */ ephy_web_view_freeze_history (view); webkit_web_view_load_alternate_html (WEBKIT_WEB_VIEW (view), html->str, uri, 0); - g_string_free (html, TRUE); } static gboolean diff --git a/lib/ephy-output-encoding.c b/lib/ephy-output-encoding.c new file mode 100644 index 0000000000000000000000000000000000000000..7256059eda3b37d3c96bfa24750397dc4a952b69 --- /dev/null +++ b/lib/ephy-output-encoding.c @@ -0,0 +1,74 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Copyright © Red Hat Inc. + * + * This file is part of Epiphany. + * + * Epiphany is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Epiphany is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Epiphany. If not, see . + */ + +#include "config.h" +#include "ephy-output-encoding.h" + +#include + +char * +ephy_encode_for_html_entity (const char *input) +{ + GString *str = g_string_new (input); + + g_string_replace (str, "&", "&", 0); + g_string_replace (str, "<", "<", 0); + g_string_replace (str, ">", ">", 0); + g_string_replace (str, "\"", """, 0); + g_string_replace (str, "'", "'", 0); + g_string_replace (str, "/", "/", 0); + + return g_string_free (str, FALSE); +} + +static char * +encode_all_except_alnum (const char *input, + const char *format) +{ + GString *str; + const char *c = input; + + if (!g_utf8_validate (input, -1, NULL)) + return g_strdup (""); + + str = g_string_new (NULL); + do { + gunichar u = g_utf8_get_char (c); + if (g_unichar_isalnum (u)) + g_string_append_unichar (str, u); + else + g_string_append_printf (str, format, u); + c = g_utf8_next_char (c); + } while (*c); + + return g_string_free (str, FALSE); +} + +char * +ephy_encode_for_html_attribute (const char *input) +{ + return encode_all_except_alnum (input, "&#x%02x;"); +} + +char * +ephy_encode_for_javascript (const char *input) +{ + return encode_all_except_alnum (input, "\\u%04u;"); +} diff --git a/lib/ephy-output-encoding.h b/lib/ephy-output-encoding.h new file mode 100644 index 0000000000000000000000000000000000000000..7ff6a33bd58d941950ae9c8c7c350b112d285a22 --- /dev/null +++ b/lib/ephy-output-encoding.h @@ -0,0 +1,38 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Copyright © 2021 Red Hat Inc. + * + * This file is part of Epiphany. + * + * Epiphany is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Epiphany is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Epiphany. If not, see . + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +/* These functions implement the OWASP XSS prevention output encoding rules: + * https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#output-encoding-rules-summary + * + * You must *carefully* read that document to safely inject untrusted data into + * web content. Here be dragons. + */ + +char *ephy_encode_for_html_entity (const char *input); +char *ephy_encode_for_html_attribute (const char *input); +char *ephy_encode_for_javascript (const char *input); + +G_END_DECLS diff --git a/lib/meson.build b/lib/meson.build index 894589a1af54ef0793bb18fe9e6d5e9f45d16387..264f9c5fb66268fc248f780b8d1c06c8291f856d 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -20,6 +20,7 @@ libephymisc_sources = [ 'ephy-langs.c', 'ephy-notification.c', 'ephy-notification-container.c', + 'ephy-output-encoding.c', 'ephy-permissions-manager.c', 'ephy-profile-utils.c', 'ephy-search-engine-manager.c',