Skip to content

Commit 1ded359

Browse files
authored
Merge pull request #53665 from nextcloud/fix/catch-exception-in-encrypt-all
fix(encryption): Catch exceptions in encrypt-all command and continue
2 parents 6d8ef87 + 4427050 commit 1ded359

File tree

3 files changed

+60
-15
lines changed

3 files changed

+60
-15
lines changed

apps/encryption/lib/Crypto/EncryptAll.php

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use OCP\Mail\Headers\AutoSubmitted;
2121
use OCP\Mail\IMailer;
2222
use OCP\Security\ISecureRandom;
23+
use Psr\Log\LoggerInterface;
2324
use Symfony\Component\Console\Helper\ProgressBar;
2425
use Symfony\Component\Console\Helper\QuestionHelper;
2526
use Symfony\Component\Console\Helper\Table;
@@ -50,6 +51,7 @@ public function __construct(
5051
protected IFactory $l10nFactory,
5152
protected QuestionHelper $questionHelper,
5253
protected ISecureRandom $secureRandom,
54+
protected LoggerInterface $logger,
5355
) {
5456
// store one time passwords for the users
5557
$this->userPasswords = [];
@@ -207,9 +209,22 @@ protected function encryptUsersFiles($uid, ProgressBar $progress, $userCount) {
207209
} else {
208210
$progress->setMessage("encrypt files for user $userCount: $path");
209211
$progress->advance();
210-
if ($this->encryptFile($path) === false) {
211-
$progress->setMessage("encrypt files for user $userCount: $path (already encrypted)");
212+
try {
213+
if ($this->encryptFile($path) === false) {
214+
$progress->setMessage("encrypt files for user $userCount: $path (already encrypted)");
215+
$progress->advance();
216+
}
217+
} catch (\Exception $e) {
218+
$progress->setMessage("Failed to encrypt path $path: " . $e->getMessage());
212219
$progress->advance();
220+
$this->logger->error(
221+
'Failed to encrypt path {path}',
222+
[
223+
'user' => $uid,
224+
'path' => $path,
225+
'exception' => $e,
226+
]
227+
);
213228
}
214229
}
215230
}
@@ -234,7 +249,14 @@ protected function encryptFile($path) {
234249
$target = $path . '.encrypted.' . time();
235250

236251
try {
237-
$this->rootView->copy($source, $target);
252+
$copySuccess = $this->rootView->copy($source, $target);
253+
if ($copySuccess === false) {
254+
/* Copy failed, abort */
255+
if ($this->rootView->file_exists($target)) {
256+
$this->rootView->unlink($target);
257+
}
258+
throw new \Exception('Copy failed for ' . $source);
259+
}
238260
$this->rootView->rename($target, $source);
239261
} catch (DecryptionFailedException $e) {
240262
if ($this->rootView->file_exists($target)) {

apps/encryption/tests/Crypto/EncryptAllTest.php

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use OCP\Security\ISecureRandom;
2424
use OCP\UserInterface;
2525
use PHPUnit\Framework\MockObject\MockObject;
26+
use Psr\Log\LoggerInterface;
2627
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
2728
use Symfony\Component\Console\Helper\ProgressBar;
2829
use Symfony\Component\Console\Helper\QuestionHelper;
@@ -46,6 +47,7 @@ class EncryptAllTest extends TestCase {
4647
protected \Symfony\Component\Console\Output\OutputInterface&MockObject $outputInterface;
4748
protected UserInterface&MockObject $userInterface;
4849
protected ISecureRandom&MockObject $secureRandom;
50+
protected LoggerInterface&MockObject $logger;
4951

5052
protected EncryptAll $encryptAll;
5153

@@ -76,6 +78,7 @@ protected function setUp(): void {
7678
->disableOriginalConstructor()->getMock();
7779
$this->userInterface = $this->getMockBuilder(UserInterface::class)
7880
->disableOriginalConstructor()->getMock();
81+
$this->logger = $this->createMock(LoggerInterface::class);
7982

8083
/**
8184
* We need format method to return a string
@@ -106,12 +109,13 @@ protected function setUp(): void {
106109
$this->l,
107110
$this->l10nFactory,
108111
$this->questionHelper,
109-
$this->secureRandom
112+
$this->secureRandom,
113+
$this->logger,
110114
);
111115
}
112116

113117
public function testEncryptAll(): void {
114-
/** @var EncryptAll | \PHPUnit\Framework\MockObject\MockObject $encryptAll */
118+
/** @var EncryptAll&MockObject $encryptAll */
115119
$encryptAll = $this->getMockBuilder(EncryptAll::class)
116120
->setConstructorArgs(
117121
[
@@ -125,7 +129,8 @@ public function testEncryptAll(): void {
125129
$this->l,
126130
$this->l10nFactory,
127131
$this->questionHelper,
128-
$this->secureRandom
132+
$this->secureRandom,
133+
$this->logger,
129134
]
130135
)
131136
->onlyMethods(['createKeyPairs', 'encryptAllUsersFiles', 'outputPasswords'])
@@ -140,7 +145,7 @@ public function testEncryptAll(): void {
140145
}
141146

142147
public function testEncryptAllWithMasterKey(): void {
143-
/** @var EncryptAll | \PHPUnit\Framework\MockObject\MockObject $encryptAll */
148+
/** @var EncryptAll&MockObject $encryptAll */
144149
$encryptAll = $this->getMockBuilder(EncryptAll::class)
145150
->setConstructorArgs(
146151
[
@@ -154,7 +159,8 @@ public function testEncryptAllWithMasterKey(): void {
154159
$this->l,
155160
$this->l10nFactory,
156161
$this->questionHelper,
157-
$this->secureRandom
162+
$this->secureRandom,
163+
$this->logger,
158164
]
159165
)
160166
->onlyMethods(['createKeyPairs', 'encryptAllUsersFiles', 'outputPasswords'])
@@ -170,7 +176,7 @@ public function testEncryptAllWithMasterKey(): void {
170176
}
171177

172178
public function testCreateKeyPairs(): void {
173-
/** @var EncryptAll | \PHPUnit\Framework\MockObject\MockObject $encryptAll */
179+
/** @var EncryptAll&MockObject $encryptAll */
174180
$encryptAll = $this->getMockBuilder(EncryptAll::class)
175181
->setConstructorArgs(
176182
[
@@ -184,7 +190,8 @@ public function testCreateKeyPairs(): void {
184190
$this->l,
185191
$this->l10nFactory,
186192
$this->questionHelper,
187-
$this->secureRandom
193+
$this->secureRandom,
194+
$this->logger,
188195
]
189196
)
190197
->onlyMethods(['setupUserFS', 'generateOneTimePassword'])
@@ -234,7 +241,8 @@ public function testEncryptAllUsersFiles(): void {
234241
$this->l,
235242
$this->l10nFactory,
236243
$this->questionHelper,
237-
$this->secureRandom
244+
$this->secureRandom,
245+
$this->logger,
238246
]
239247
)
240248
->onlyMethods(['encryptUsersFiles'])
@@ -275,7 +283,8 @@ public function testEncryptUsersFiles(): void {
275283
$this->l,
276284
$this->l10nFactory,
277285
$this->questionHelper,
278-
$this->secureRandom
286+
$this->secureRandom,
287+
$this->logger,
279288
]
280289
)
281290
->onlyMethods(['encryptFile', 'setupUserFS'])

lib/private/Files/Storage/Wrapper/Encryption.php

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use OCP\Encryption\Keys\IStorage;
2424
use OCP\Files;
2525
use OCP\Files\Cache\ICacheEntry;
26+
use OCP\Files\GenericFileException;
2627
use OCP\Files\Mount\IMountPoint;
2728
use OCP\Files\Storage;
2829
use Psr\Log\LoggerInterface;
@@ -685,12 +686,16 @@ private function copyBetweenStorage(
685686
try {
686687
$source = $sourceStorage->fopen($sourceInternalPath, 'r');
687688
$target = $this->fopen($targetInternalPath, 'w');
688-
[, $result] = Files::streamCopy($source, $target, true);
689+
if ($source === false || $target === false) {
690+
$result = false;
691+
} else {
692+
[, $result] = Files::streamCopy($source, $target, true);
693+
}
689694
} finally {
690-
if (is_resource($source)) {
695+
if (isset($source) && $source !== false) {
691696
fclose($source);
692697
}
693-
if (is_resource($target)) {
698+
if (isset($target) && $target !== false) {
694699
fclose($target);
695700
}
696701
}
@@ -740,6 +745,9 @@ public function stat(string $path): array|false {
740745

741746
public function hash(string $type, string $path, bool $raw = false): string|false {
742747
$fh = $this->fopen($path, 'rb');
748+
if ($fh === false) {
749+
return false;
750+
}
743751
$ctx = hash_init($type);
744752
hash_update_stream($ctx, $fh);
745753
fclose($fh);
@@ -764,6 +772,9 @@ protected function readFirstBlock(string $path): string {
764772
$firstBlock = '';
765773
if ($this->storage->is_file($path)) {
766774
$handle = $this->storage->fopen($path, 'r');
775+
if ($handle === false) {
776+
return '';
777+
}
767778
$firstBlock = fread($handle, $this->util->getHeaderSize());
768779
fclose($handle);
769780
}
@@ -894,6 +905,9 @@ protected function shouldEncrypt(string $path): bool {
894905
public function writeStream(string $path, $stream, ?int $size = null): int {
895906
// always fall back to fopen
896907
$target = $this->fopen($path, 'w');
908+
if ($target === false) {
909+
throw new GenericFileException("Failed to open $path for writing");
910+
}
897911
[$count, $result] = Files::streamCopy($stream, $target, true);
898912
fclose($stream);
899913
fclose($target);

0 commit comments

Comments
 (0)