[pbs-devel] [PATCH proxmox-backup v2] tape: skip setting encryption if we can't and don't want to

Dominik Csapak d.csapak at proxmox.com
Fri Jul 11 15:29:51 CEST 2025


On 7/11/25 13:45, Thomas Lamprecht wrote:
> The subject reads a bit sassy ^^
> 
> 
> Am 16.04.25 um 09:07 schrieb Dominik Csapak:
>> Some settings on changers prevents changing the encryption parameters
>> via the application, e.g. some libraries have a 'encryption disabled' or
>> 'encryption is library managed' option. While the former situation can
>> be fixed by setting the library to 'application managed', the latter is
>> sometimes necessary for FIPS compliance (to ensure the tape data is
>> encrypted).
>>
>> When libraries are configured this way, the code currently fails with
>> 'drive does not support AES-GCM encryption'. Instead of failing, check
>> on first call to set_encryption if we could set it, and save that
>> result.
> 
> Could that result in a transient transport/HW/... error becoming
> permanent? I mean, probably not hugely likely, but with such "automagic"
> behavior it might be worth about thinking of some potential exit
> strategy for the user, or is the lifetime bound to only a single job
> anyway – I could naturally check myself, but might be nice to mention
> that here.

no that is just per job/action so each time we open the tape drive.
can reword that part to make it clearer

> 
>>
>> Only fail when encryption is to be enabled but it is not allowed, but
>> ignore the error when the backup should be done unencrypted.
>>
>> `assert_encryption_mode` must also check if it's possible, and skip any
>> error if it's not possible and we wanted no encryption.
>>
>> With these changes, it should be possible to use such configured libraries
>> when there is no encryption configured on the PBS side. (We currently
>> don't have a library with such capabilities to test.)
>>
>> Note that in contrast to normal operation, the tape label will also be
>> encrypted then and will not be readable in case the encryption key is
>> lost or changed.
>>
>> Additionally, return an error for 'drive_set_encryption' in case the
>> drive reports that it does not support hardware encryption, because this
>> is now already caught one level above in 'set_encryption'.
> 
> nit: "already" implies to me that it's checked upfront or handled by a
> method called, not one that calls us.
> 
> "[...] because this is now handled a level above"
> 
> is IMO a bit better fitting
> 

ok

>>
>> Also, slightly change the error message to make it clear that the drive
>> does not support *setting* encryption, not that it does not support
>> it at all.
>>
>> This was reported in the community forum:
>>
>> https://forum.proxmox.com/threads/107383/
>> https://forum.proxmox.com/threads/164941/
>>
>> Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
>> ---
>> changes from v1:
>> * adapted error message to make it clear what is not supported
>> * also adapt `assert_encryption_mode`, otherwise a backup is not possible
>>
>>   pbs-tape/src/sg_tape.rs            | 30 +++++++++++++++++++++++++++++-
>>   pbs-tape/src/sg_tape/encryption.rs | 12 ++----------
>>   src/tape/drive/lto/mod.rs          |  4 ++++
>>   3 files changed, 35 insertions(+), 11 deletions(-)
>>
>> diff --git a/pbs-tape/src/sg_tape.rs b/pbs-tape/src/sg_tape.rs
>> index 39e6a2f94..9c7a254d5 100644
>> --- a/pbs-tape/src/sg_tape.rs
>> +++ b/pbs-tape/src/sg_tape.rs
>> @@ -136,6 +136,8 @@ pub struct SgTape {
>>       file: File,
>>       locate_offset: Option<i64>,
>>       info: InquiryInfo,
>> +    // auto-detect if we can set the encryption mode
>> +    encryption_possible: Option<bool>,
>>   }
>>   
>>   impl SgTape {
>> @@ -158,6 +160,7 @@ impl SgTape {
>>               file,
>>               info,
>>               locate_offset: None,
>> +            encryption_possible: None,
>>           })
>>       }
>>   
>> @@ -690,6 +693,14 @@ impl SgTape {
>>       }
>>   
>>       pub fn set_encryption(&mut self, key_data: Option<([u8; 32], Uuid)>) -> Result<(), Error> {
>> +        if self.encryption_possible == Some(false) {
>> +            if key_data.is_some() {
>> +                bail!("Drive does not support setting encryption mode");
>> +            } else {
>> +                // skip trying to set encryption if not supported and don't wanted
> 
> s/don't/not/ and s/set/clear/ (still not ideal but slightly better IMO).
> 
> btw. if one enabled encryption before changing the library to FIPS (or
> whatever blocks this setting then) and then cleared encryption we might
> then signal that it work the next time this method is called while it
> did not, seems a bit odd.
> 
> Albeit, if that question even makes sense depends on the lifetime of the
> encryption_possible setting.
> 

since it's just for one job/action I don't think that's an issue, since
modifying drive/changer settings during such an operation won't work most of
the time anyway (since those things like to block during operation)

> Did you evaluate if exposing a setting the user has to explicitly enable?
> Not saying that is better, but might be a bit simpler to understand and
> probably also slightly safer to implement (or at least review hehe), I think.
> OTOTH., not much in the tape code atm., so my questions might be bogus and
> my reservation for this unfounded.

The original patch is already a few months old, so I'm not sure what exactly
i evaluated, but thinking about it now, I don't really want to expose
an option that 99% of users will never need and there is not much choice
in what to do anyway. One advantage of a user setting might be that
if the 'FIPS' mode (i'll just call it that for the moment) is enabled
by accident, we'd error out instead of just logging it.

Though I hope not that anybody could enable that mode by accident
(since one must manage e.g. the encryption keys, etc.)

> 
>> +                return Ok(());
>> +            }
>> +        }
>>           let key = if let Some((ref key, ref uuid)) = key_data {
>>               // derive specialized key for each media-set
>>   
>> @@ -710,7 +721,24 @@ impl SgTape {
>>               None
>>           };
>>   
>> -        drive_set_encryption(&mut self.file, key)
>> +        match drive_set_encryption(&mut self.file, key) {
>> +            Ok(()) => self.encryption_possible = Some(true),
>> +            Err(err) => {
>> +                self.encryption_possible = Some(false);
>> +                if key.is_some() {
>> +                    bail!("could not set encryption mode on drive: {err}");
>> +                } else {
>> +                    log::info!("could not set encryption mode on drive: {err}, ignoring.");
> 
> While internally naming the function "set" might be fine, it always sets some
> value after all, but I'd not leak that outside and rather do s/set/clear/ for
> above log message, as otherwise the user will interpret this as we tried to
> set (= enable) encryption, failed and just ignore it, which seems rather unsafe
> from their POV.

yes makes sense

> 
>> +                }
>> +            }
>> +        }
>> +        Ok(())
>> +    }
>> +
>> +    /// Returns if encryption is possible. Returns [`None`] if it's unknown, because
>> +    /// no attempt was made to set the mode yet.
>> +    pub fn encryption_possible(&self) -> Option<bool> {
>> +        self.encryption_possible
>>       }
>>   
>>       // Note: use alloc_page_aligned_buffer to alloc data transfer buffer
>> diff --git a/pbs-tape/src/sg_tape/encryption.rs b/pbs-tape/src/sg_tape/encryption.rs
>> index 7247d257f..c24a6c658 100644
>> --- a/pbs-tape/src/sg_tape/encryption.rs
>> +++ b/pbs-tape/src/sg_tape/encryption.rs
>> @@ -12,15 +12,7 @@ use crate::sgutils2::{alloc_page_aligned_buffer, SgRaw};
>>   ///
>>   /// We always use mixed mode,
>>   pub fn drive_set_encryption<F: AsRawFd>(file: &mut F, key: Option<[u8; 32]>) -> Result<(), Error> {
>> -    let data = match sg_spin_data_encryption_caps(file) {
>> -        Ok(data) => data,
>> -        Err(_) if key.is_none() => {
>> -            // Assume device does not support HW encryption
>> -            // We can simply ignore the clear key request
>> -            return Ok(());
>> -        }
>> -        Err(err) => return Err(err),
>> -    };
>> +    let data = sg_spin_data_encryption_caps(file)?;
>>   
>>       let algorithm_index = decode_spin_data_encryption_caps(&data)?;
>>   
>> @@ -266,7 +258,7 @@ fn decode_spin_data_encryption_caps(data: &[u8]) -> Result<u8, Error> {
>>   
>>           match aes_gcm_index {
>>               Some(index) => Ok(index),
>> -            None => bail!("drive does not support AES-GCM encryption"),
>> +            None => bail!("drive does not support setting AES-GCM encryption"),
>>           }
>>       })
>>       .map_err(|err: Error| format_err!("decode data encryption caps page failed - {}", err))
>> diff --git a/src/tape/drive/lto/mod.rs b/src/tape/drive/lto/mod.rs
>> index 23e043ce6..bd5ec8ae6 100644
>> --- a/src/tape/drive/lto/mod.rs
>> +++ b/src/tape/drive/lto/mod.rs
>> @@ -264,6 +264,10 @@ impl TapeDriver for LtoTapeHandle {
>>       }
>>   
>>       fn assert_encryption_mode(&mut self, encryption_wanted: bool) -> Result<(), Error> {
>> +        if Some(false) == self.sg_tape.encryption_possible() && !encryption_wanted {
>> +            // ignore assertion for unencrypted mode, because we can't set it anyway
> 
> s/because/if/ ?

sure, sounds also good to me

I'll send a v3. Since the changes are only rewordings of the message and
comments, I'll include the 'tested-by' tag from Andrew Stone, alright?

> 
>> +            return Ok(());
>> +        }
>>           let encryption_set = drive_get_encryption(self.sg_tape.file_mut())?;
>>           if encryption_wanted != encryption_set {
>>               bail!("Set encryption mode not what was desired (set: {encryption_set}, wanted: {encryption_wanted})");
> 





More information about the pbs-devel mailing list