3030#endif
3131
3232#include " arrow/util/secure_string.h"
33+ #include " arrow/util/logging.h"
3334#include " arrow/util/span.h"
3435
3536namespace arrow ::util {
3637
37- SecureString::SecureString (SecureString&& other) noexcept
38- : secret_(std::move(other.secret_)) {
38+ // / Note:
39+ // / A string std::string is securely moved into a SecureString in two steps:
40+ // / 1. the std::string is moved via std::move(string)
41+ // / 2. the std::string is securely cleared
42+ // /
43+ // / The std::move has two different effects, depending on the size of the string.
44+ // / A very short string (called local string) stores the string in a local buffer,
45+ // / a long string stores a pointer to allocated memory that stores the string.
46+ // /
47+ // / If the string is a small string, std::move copies the local buffer.
48+ // / If the string is a long string, std::move moves the pointer and then resets the
49+ // / string size to 0 (which turns the string into a local string).
50+ // /
51+ // / In both cases, after a std::move(string), the string uses the local buffer.
52+ // /
53+ // / Thus, after a std::move(string), calling SecureClear(std::string*) only
54+ // / securely clears the **local buffer** of the string. Therefore, std::move(string)
55+ // / must move the pointer of long string into SecureString (which later clears the string).
56+ // / Otherwise, the content of the string cannot be securely cleared.
57+ // /
58+ // / This condition is checked by secure_move.
59+
60+ void secure_move (std::string& string, std::string& dst) {
61+ auto ptr = string.data ();
62+ dst = std::move (string);
63+
64+ // We require the buffer address string.data() to remain (not be freed),
65+ // or reused by dst. Otherwise, we cannot securely clear string after this move
66+ ARROW_CHECK (string.data () == ptr || dst.data () == ptr);
67+ }
68+
69+ SecureString::SecureString (SecureString&& other) noexcept {
70+ secure_move (other.secret_ , secret_);
3971 other.Dispose ();
4072}
4173
42- SecureString::SecureString (std::string&& secret) noexcept : secret_(std::move(secret)) {
74+ SecureString::SecureString (std::string&& secret) noexcept {
75+ secure_move (secret, secret_);
4376 SecureClear (&secret);
4477}
4578
@@ -51,7 +84,7 @@ SecureString& SecureString::operator=(SecureString&& other) noexcept {
5184 return *this ;
5285 }
5386 Dispose ();
54- secret_ = std::move (other.secret_ );
87+ secure_move (other.secret_ , secret_);
5588 other.Dispose ();
5689 return *this ;
5790}
@@ -68,16 +101,7 @@ SecureString& SecureString::operator=(const SecureString& other) {
68101
69102SecureString& SecureString::operator =(std::string&& secret) noexcept {
70103 Dispose ();
71- // std::string implementation may distinguish between local string (a very short string)
72- // and non-local (longer) strings. The former stores the string in a local buffer, the
73- // latter stores a pointer to allocated memory that stores the string.
74- //
75- // If secret is a local string, copies local buffer, resets size to 0
76- // - requires secure cleaning the local buffer
77- // If secret is longer, moves the pointer to secret_, resets to 0, uses local buffer
78- // - does not require cleaning anything
79- secret_ = std::move (secret);
80- // cleans only the local buffer of secret as this always is a local string by now
104+ secure_move (secret, secret_);
81105 SecureClear (&secret);
82106 return *this ;
83107}
0 commit comments