mirror of
https://github.com/veracrypt/VeraCrypt.git
synced 2026-10-22 14:22:36 +00:00
Unix: reject stdin password overflow immediately
Reject oversized input for commands that use the password without waiting for a newline or EOF from the writer. Correct the documentation to describe locale decoding followed by UTF-8 conversion, without equating it with the interactive prompt.
This commit is contained in:
@@ -12,6 +12,8 @@ here with --stdin. Checked:
|
||||
- no input, an invalid byte, an incomplete character at the end of input
|
||||
and a line longer than a password can be fail with their own messages
|
||||
instead of becoming a shorter or empty password;
|
||||
- oversized input is rejected even if the writer keeps the pipe open
|
||||
without sending a line feed;
|
||||
- only the first line is used, a final line feed is optional, and a
|
||||
carriage return is part of the password;
|
||||
- NUL bytes are part of the password: a volume created with "te\\0st"
|
||||
@@ -27,6 +29,7 @@ here with --stdin. Checked:
|
||||
import argparse
|
||||
import os
|
||||
import shutil
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
@@ -49,6 +52,40 @@ class Checks:
|
||||
print(" " + detail.strip().replace("\n", "\n "))
|
||||
|
||||
|
||||
def check_overflow_without_eof(vc, checks, work, env):
|
||||
path = work / "overflow.hc"
|
||||
read_fd, write_fd = os.pipe()
|
||||
try:
|
||||
# This fits in a Linux pipe and exceeds the reader's raw input cap.
|
||||
os.write(write_fd, b"x" * 4096)
|
||||
with subprocess.Popen(
|
||||
[vc, "--text", "--non-interactive", "--stdin", "--create",
|
||||
str(path)], stdin=read_fd, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT, env=env,
|
||||
start_new_session=True) as process:
|
||||
try:
|
||||
# Keep write_fd open: communicate must not supply EOF.
|
||||
output, _ = process.communicate(timeout=10)
|
||||
except subprocess.TimeoutExpired:
|
||||
# Stop the core service too, since it inherits the output pipe.
|
||||
try:
|
||||
os.killpg(process.pid, signal.SIGKILL)
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
process.communicate()
|
||||
checks.check("oversized input is rejected without EOF", False,
|
||||
"VeraCrypt waited for the writer to close")
|
||||
else:
|
||||
checks.check("oversized input is rejected without EOF",
|
||||
process.returncode != 0
|
||||
and b"The password from standard input is too long" in output
|
||||
and not path.exists(),
|
||||
output.decode("utf-8", "replace"))
|
||||
finally:
|
||||
os.close(read_fd)
|
||||
os.close(write_fd)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--veracrypt", default="/usr/bin/veracrypt")
|
||||
@@ -109,6 +146,7 @@ def main():
|
||||
print("skip " + reason)
|
||||
|
||||
try:
|
||||
check_overflow_without_eof(vc, c, work, utf8)
|
||||
ok, r = mount(volume, b"test\n", target=mnt)
|
||||
c.check("the first line is the password", ok, text(r))
|
||||
for desc, data, want in (
|
||||
|
||||
@@ -269,7 +269,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td><em>--stdin</em></td>
|
||||
<td>Read the password from standard input. This option can be used only with <em>--non-interactive</em> and cannot be combined with <em>--password</em>. The password is the first line; a final line feed is optional, and a carriage return or NUL byte is part of the password. It is decoded in the character set of the current locale (LC_CTYPE), like a password typed at the prompt. An empty line is an empty password (for keyfiles only). For commands that use the password, missing input, input that is not valid in that character set, and a line longer than a password can be are errors.</td>
|
||||
<td>Read the password from standard input. This option can be used only with <em>--non-interactive</em> and cannot be combined with <em>--password</em>. The password is the first line; a final line feed is optional, and a carriage return or NUL byte is part of the password. It is decoded in the character set of the active locale (LC_CTYPE), then converted to UTF-8 for use as the volume password. An empty line is an empty password (for keyfiles only). For commands that use the password, missing input, input that is not valid in that character set, and a line longer than a password can be are errors.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><em>-t</em> or <em>--text</em></td>
|
||||
|
||||
+24
-16
@@ -1234,12 +1234,26 @@ const FileManager fileManagers[] = {
|
||||
|
||||
if (Preferences.UseStandardInput)
|
||||
{
|
||||
bool commandUsesPassword = false;
|
||||
switch (cmdLine.ArgCommand)
|
||||
{
|
||||
case CommandId::AutoMountDevices:
|
||||
case CommandId::AutoMountFavorites:
|
||||
case CommandId::AutoMountDevicesFavorites:
|
||||
case CommandId::MountVolume:
|
||||
case CommandId::CreateVolume:
|
||||
case CommandId::ChangePassword:
|
||||
commandUsesPassword = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// The password is the first line of standard input (a final line
|
||||
// feed is optional). It is read as bytes into a fixed, wiped buffer
|
||||
// and decoded as a whole in the character set of LC_CTYPE, like a
|
||||
// password typed at the prompt: decoding while reading (wcin)
|
||||
// silently stopped at an invalid or incomplete character. Only the
|
||||
// first line is read.
|
||||
// and decoded as a whole in the character set of LC_CTYPE. Decoding
|
||||
// while reading (wcin) silently stopped at an invalid or incomplete
|
||||
// character. Only the first line is read.
|
||||
SecureBuffer pwdBytes (VolumePassword::MaxSize * MB_LEN_MAX);
|
||||
size_t pwdLength = 0;
|
||||
bool pwdTooLong = false;
|
||||
@@ -1250,7 +1264,12 @@ const FileManager fileManagers[] = {
|
||||
if (pwdLength < pwdBytes.Size())
|
||||
pwdBytes.Ptr()[pwdLength++] = (uint8) c;
|
||||
else
|
||||
{
|
||||
// The writer may never close the pipe or send a line feed.
|
||||
if (commandUsesPassword)
|
||||
throw_err (L"The password from standard input is too long");
|
||||
pwdTooLong = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool pwdRead = !ferror (stdin) && (c == '\n' || pwdLength > 0);
|
||||
@@ -1260,25 +1279,14 @@ const FileManager fileManagers[] = {
|
||||
bool pwdDecoded = pwdRead && !pwdTooLong
|
||||
&& DecodeStandardInputPassword (pwdBytes.Ptr(), pwdLength, pwdInput);
|
||||
|
||||
switch (cmdLine.ArgCommand)
|
||||
if (commandUsesPassword)
|
||||
{
|
||||
case CommandId::AutoMountDevices:
|
||||
case CommandId::AutoMountFavorites:
|
||||
case CommandId::AutoMountDevicesFavorites:
|
||||
case CommandId::MountVolume:
|
||||
case CommandId::CreateVolume:
|
||||
case CommandId::ChangePassword:
|
||||
// A failed read must not become a shorter or empty password
|
||||
// (keyfiles may open the volume with one).
|
||||
if (!pwdRead)
|
||||
throw_err (L"Cannot read the password from standard input");
|
||||
if (pwdTooLong)
|
||||
throw_err (L"The password from standard input is too long");
|
||||
if (!pwdDecoded)
|
||||
throw_err (StringFormatter (L"The password from standard input is not valid in the character encoding of the current locale ({0})", wstring (StringConverter::ToWide (nl_langinfo (CODESET)))));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!pwdDecoded)
|
||||
|
||||
Reference in New Issue
Block a user