lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <56364bd9-2a6f-40f5-972c-f62898284a5c@redhat.com>
Date: Fri, 7 Feb 2025 18:05:44 +0100
From: Jocelyn Falempe <jfalempe@...hat.com>
To: Maarten Lankhorst <maarten.lankhorst@...ux.intel.com>,
 Maxime Ripard <mripard@...nel.org>, Thomas Zimmermann <tzimmermann@...e.de>,
 David Airlie <airlied@...il.com>, Simona Vetter <simona@...ll.ch>,
 Miguel Ojeda <ojeda@...nel.org>, Thomas Böhler
 <witcher@...edspace.de>, rust-for-linux@...r.kernel.org,
 dri-devel@...ts.freedesktop.org, linux-kernel@...r.kernel.org
Subject: Re: [PATCH v2] drm/panic: Better binary encoding in QR code

On 28/01/2025 17:52, Jocelyn Falempe wrote:
> The current encoding, is done by converting 13bits of input into 4
> decimal digits, that are then encoded efficiently using the numeric
> encoding of the QR code specification.
> The Fido v2.2 specification [1] uses a similar approach for its
> QR-initiated authentication. The only difference is that it converts
> 7 bytes (56bits) of input into 17 decimal digits. The benefit is that
> the algorithm doesn't require to split input bytes into 13bits chunk,
> and the ratio is a bit better.
> This improvement was proposed by Jó Ágila Bitsch in [2].
> drm_panic is still young, and the QR code feature is not widely used,
> so it's still time to switch to a common algorithm, shared with a
> widely used standard.
> I also changed the name of the url parameter, from zl= to z=, so the
> website can keep backward compatibility if needed.
> 
> [1] https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#hybrid-qr-initiated
> [2] https://github.com/kdj0c/panic_report/issues/2
> 
> Signed-off-by: Jocelyn Falempe <jfalempe@...hat.com>

As it had no reviews for 2 months, I've got an Acked-by from Maxime 
Ripard off-list, and pushed it to drm-misc-next.

Thanks,

-- 

Jocelyn


> ---
> 
> v2:
>   * Add comments for the SegmentIterator numeric encoding.
>   
>   
>   drivers/gpu/drm/drm_panic.c     |   2 +-
>   drivers/gpu/drm/drm_panic_qr.rs | 108 +++++++++++---------------------
>   2 files changed, 36 insertions(+), 74 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_panic.c b/drivers/gpu/drm/drm_panic.c
> index f128d345b16d..ab42a2b1567d 100644
> --- a/drivers/gpu/drm/drm_panic.c
> +++ b/drivers/gpu/drm/drm_panic.c
> @@ -499,7 +499,7 @@ static int drm_panic_get_qr_code_url(u8 **qr_image)
>   	char *kmsg;
>   	int max_qr_data_size, url_len;
>   
> -	url_len = snprintf(url, sizeof(url), CONFIG_DRM_PANIC_SCREEN_QR_CODE_URL "?a=%s&v=%s&zl=",
> +	url_len = snprintf(url, sizeof(url), CONFIG_DRM_PANIC_SCREEN_QR_CODE_URL "?a=%s&v=%s&z=",
>   			   utsname()->machine, utsname()->release);
>   
>   	max_qr_data_size = drm_panic_qr_max_data_size(panic_qr_version, url_len);
> diff --git a/drivers/gpu/drm/drm_panic_qr.rs b/drivers/gpu/drm/drm_panic_qr.rs
> index 09500cddc009..477de4101c3e 100644
> --- a/drivers/gpu/drm/drm_panic_qr.rs
> +++ b/drivers/gpu/drm/drm_panic_qr.rs
> @@ -13,12 +13,13 @@
>   //! The binary data must be a valid URL parameter, so the easiest way is
>   //! to use base64 encoding. But this wastes 25% of data space, so the
>   //! whole stack trace won't fit in the QR code. So instead it encodes
> -//! every 13bits of input into 4 decimal digits, and then uses the
> +//! every 7 bytes of input into 17 decimal digits, and then uses the
>   //! efficient numeric encoding, that encode 3 decimal digits into
> -//! 10bits. This makes 39bits of compressed data into 12 decimal digits,
> -//! into 40bits in the QR code, so wasting only 2.5%. And the numbers are
> +//! 10bits. This makes 168bits of compressed data into 51 decimal digits,
> +//! into 170bits in the QR code, so wasting only 1.17%. And the numbers are
>   //! valid URL parameter, so the website can do the reverse, to get the
> -//! binary data.
> +//! binary data. This is the same algorithm used by Fido v2.2 QR-initiated
> +//! authentication specification.
>   //!
>   //! Inspired by these 3 projects, all under MIT license:
>   //!
> @@ -26,7 +27,6 @@
>   //! * <https://github.com/erwanvivien/fast_qr>
>   //! * <https://github.com/bjguillot/qr>
>   
> -use core::cmp;
>   use kernel::str::CStr;
>   
>   #[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)]
> @@ -296,35 +296,11 @@ fn version_info(&self) -> u32 {
>   /// Padding bytes.
>   const PADDING: [u8; 2] = [236, 17];
>   
> -/// Get the next 13 bits of data, starting at specified offset (in bits).
> -fn get_next_13b(data: &[u8], offset: usize) -> Option<(u16, usize)> {
> -    if offset < data.len() * 8 {
> -        let size = cmp::min(13, data.len() * 8 - offset);
> -        let byte_off = offset / 8;
> -        let bit_off = offset % 8;
> -        // `b` is 20 at max (`bit_off` <= 7 and `size` <= 13).
> -        let b = (bit_off + size) as u16;
> -
> -        let first_byte = (data[byte_off] << bit_off >> bit_off) as u16;
> -
> -        let number = match b {
> -            0..=8 => first_byte >> (8 - b),
> -            9..=16 => (first_byte << (b - 8)) + (data[byte_off + 1] >> (16 - b)) as u16,
> -            _ => {
> -                (first_byte << (b - 8))
> -                    + ((data[byte_off + 1] as u16) << (b - 16))
> -                    + (data[byte_off + 2] >> (24 - b)) as u16
> -            }
> -        };
> -        Some((number, size))
> -    } else {
> -        None
> -    }
> -}
> -
>   /// Number of bits to encode characters in numeric mode.
>   const NUM_CHARS_BITS: [usize; 4] = [0, 4, 7, 10];
> -const POW10: [u16; 4] = [1, 10, 100, 1000];
> +/// Number of decimal digits required to encode n bytes of binary data.
> +/// eg: you need 15 decimal digits to fit 6 bytes of binary data.
> +const BYTES_TO_DIGITS: [usize; 8] = [0, 3, 5, 8, 10, 13, 15, 17];
>   
>   enum Segment<'a> {
>       Numeric(&'a [u8]),
> @@ -360,13 +336,9 @@ fn character_count(&self) -> usize {
>           match self {
>               Segment::Binary(data) => data.len(),
>               Segment::Numeric(data) => {
> -                let data_bits = data.len() * 8;
> -                let last_chars = match data_bits % 13 {
> -                    1 => 1,
> -                    k => (k + 1) / 3,
> -                };
> -                // 4 decimal numbers per 13bits + remainder.
> -                4 * (data_bits / 13) + last_chars
> +                let last_chars = BYTES_TO_DIGITS[data.len() % 7];
> +                // 17 decimal numbers per 7bytes + remainder.
> +                17 * (data.len() / 7) + last_chars
>               }
>           }
>       }
> @@ -403,7 +375,7 @@ fn iter(&self) -> SegmentIterator<'_> {
>   struct SegmentIterator<'a> {
>       segment: &'a Segment<'a>,
>       offset: usize,
> -    carry: u16,
> +    carry: u64,
>       carry_len: usize,
>   }
>   
> @@ -422,40 +394,30 @@ fn next(&mut self) -> Option<Self::Item> {
>                   }
>               }
>               Segment::Numeric(data) => {
> -                if self.carry_len == 3 {
> -                    let out = (self.carry, NUM_CHARS_BITS[self.carry_len]);
> -                    self.carry_len = 0;
> -                    self.carry = 0;
> -                    Some(out)
> -                } else if let Some((bits, size)) = get_next_13b(data, self.offset) {
> -                    self.offset += size;
> -                    let new_chars = match size {
> -                        1 => 1,
> -                        k => (k + 1) / 3,
> -                    };
> -                    if self.carry_len + new_chars > 3 {
> -                        self.carry_len = new_chars + self.carry_len - 3;
> -                        let out = (
> -                            self.carry * POW10[new_chars - self.carry_len]
> -                                + bits / POW10[self.carry_len],
> -                            NUM_CHARS_BITS[3],
> -                        );
> -                        self.carry = bits % POW10[self.carry_len];
> -                        Some(out)
> -                    } else {
> -                        let out = (
> -                            self.carry * POW10[new_chars] + bits,
> -                            NUM_CHARS_BITS[self.carry_len + new_chars],
> -                        );
> -                        self.carry_len = 0;
> -                        Some(out)
> +                if self.carry_len < 3 && self.offset < data.len() {
> +                    // If there are less than 3 decimal digits in the carry,
> +                    // take the next 7 bytes of input, and add them to the carry.
> +                    let mut buf = [0u8; 8];
> +                    let len = 7.min(data.len() - self.offset);
> +                    buf[..len].copy_from_slice(&data[self.offset..self.offset + len]);
> +                    let chunk = u64::from_le_bytes(buf);
> +                    let pow = u64::pow(10, BYTES_TO_DIGITS[len] as u32);
> +                    self.carry = chunk + self.carry * pow;
> +                    self.offset += len;
> +                    self.carry_len += BYTES_TO_DIGITS[len];
> +                }
> +                match self.carry_len {
> +                    0 => None,
> +                    len => {
> +                        // take the next 3 decimal digits of the carry
> +                        // and return 10bits of numeric data.
> +                        let out_len = 3.min(len);
> +                        self.carry_len -= out_len;
> +                        let pow = u64::pow(10, self.carry_len as u32);
> +                        let out = (self.carry / pow) as u16;
> +                        self.carry = self.carry % pow;
> +                        Some((out, NUM_CHARS_BITS[out_len]))
>                       }
> -                } else if self.carry_len > 0 {
> -                    let out = (self.carry, NUM_CHARS_BITS[self.carry_len]);
> -                    self.carry_len = 0;
> -                    Some(out)
> -                } else {
> -                    None
>                   }
>               }
>           }
> 
> base-commit: e2a81c0cd7de6cb063058be304b18f200c64802b


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ