Coverage Report

Created: 2025-07-07 10:01

/src/libreoffice/sfx2/source/doc/docmacromode.cxx
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 *
9
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include <config_features.h>
21
22
#include <sfx2/docmacromode.hxx>
23
#include <sfx2/signaturestate.hxx>
24
#include <sfx2/docfile.hxx>
25
26
#include <com/sun/star/document/MacroExecMode.hpp>
27
#include <com/sun/star/task/ErrorCodeRequest.hpp>
28
#include <com/sun/star/task/DocumentMacroConfirmationRequest.hpp>
29
#include <com/sun/star/security/DocumentDigitalSignatures.hpp>
30
#include <com/sun/star/script/XLibraryContainer.hpp>
31
#include <com/sun/star/document/XEmbeddedScripts.hpp>
32
33
#include <comphelper/processfactory.hxx>
34
#include <framework/interaction.hxx>
35
#include <osl/file.hxx>
36
#include <unotools/securityoptions.hxx>
37
#include <svtools/sfxecode.hxx>
38
#include <comphelper/diagnose_ex.hxx>
39
#include <tools/urlobj.hxx>
40
41
#if defined(_WIN32)
42
#include <o3tl/char16_t2wchar_t.hxx>
43
#include <officecfg/Office/Common.hxx>
44
#include <systools/win32/comtools.hxx>
45
#include <urlmon.h>
46
#endif
47
48
namespace sfx2
49
{
50
51
52
    using ::com::sun::star::uno::Reference;
53
    using ::com::sun::star::task::XInteractionHandler;
54
    using ::com::sun::star::uno::Any;
55
    using ::com::sun::star::uno::Sequence;
56
    using ::com::sun::star::task::DocumentMacroConfirmationRequest;
57
    using ::com::sun::star::uno::Exception;
58
    using ::com::sun::star::security::DocumentDigitalSignatures;
59
    using ::com::sun::star::security::XDocumentDigitalSignatures;
60
    using ::com::sun::star::embed::XStorage;
61
    using ::com::sun::star::document::XEmbeddedScripts;
62
    using ::com::sun::star::script::XLibraryContainer;
63
    using ::com::sun::star::container::XNameAccess;
64
    using ::com::sun::star::uno::UNO_QUERY_THROW;
65
66
    namespace MacroExecMode = ::com::sun::star::document::MacroExecMode;
67
68
69
    //= DocumentMacroMode_Data
70
71
    struct DocumentMacroMode_Data
72
    {
73
        IMacroDocumentAccess&       m_rDocumentAccess;
74
        bool m_bHasUnsignedContentError;
75
        /// Is true when macros was disabled due to invalid signatures (when macro security is high)
76
        bool m_bHasInvalidSignaturesError;
77
78
        explicit DocumentMacroMode_Data( IMacroDocumentAccess& rDocumentAccess )
79
245k
            :m_rDocumentAccess( rDocumentAccess )
80
245k
            ,m_bHasUnsignedContentError( false )
81
245k
            ,m_bHasInvalidSignaturesError( false )
82
245k
        {
83
245k
        }
84
    };
85
86
    namespace
87
    {
88
        bool lcl_showMacroWarning( const Reference< XInteractionHandler >& rxHandler,
89
            const OUString& rDocumentLocation )
90
0
        {
91
0
            DocumentMacroConfirmationRequest aRequest;
92
0
            aRequest.DocumentURL = rDocumentLocation;
93
0
            return SfxMedium::CallApproveHandler( rxHandler, Any( aRequest ), true );
94
0
        }
95
    }
96
97
    //= DocumentMacroMode
98
    DocumentMacroMode::DocumentMacroMode( IMacroDocumentAccess& rDocumentAccess )
99
245k
        :m_xData( std::make_shared<DocumentMacroMode_Data>( rDocumentAccess ) )
100
245k
    {
101
245k
    }
102
103
    bool DocumentMacroMode::allowMacroExecution()
104
226k
    {
105
226k
        m_xData->m_rDocumentAccess.setCurrentMacroExecMode( MacroExecMode::ALWAYS_EXECUTE_NO_WARN );
106
226k
        return true;
107
226k
    }
108
109
    bool DocumentMacroMode::disallowMacroExecution()
110
8.47k
    {
111
8.47k
        m_xData->m_rDocumentAccess.setCurrentMacroExecMode( MacroExecMode::NEVER_EXECUTE );
112
8.47k
        return false;
113
8.47k
    }
114
115
    bool DocumentMacroMode::adjustMacroMode( const Reference< XInteractionHandler >& rxInteraction, bool bHasValidContentSignature )
116
0
    {
117
0
        if ( SvtSecurityOptions::IsMacroDisabled() )
118
0
        {
119
            // no macro should be executed at all
120
0
            return disallowMacroExecution();
121
0
        }
122
123
        // get setting from configuration if required
124
0
        enum AutoConfirmation
125
0
        {
126
0
            eNoAutoConfirm,
127
0
            eAutoConfirmApprove,
128
0
            eAutoConfirmReject
129
0
        };
130
0
        AutoConfirmation eAutoConfirm( eNoAutoConfirm );
131
132
0
        sal_Int16 nMacroExecutionMode = m_xData->m_rDocumentAccess.getCurrentMacroExecMode();
133
0
        if  (   ( nMacroExecutionMode == MacroExecMode::USE_CONFIG )
134
0
            ||  ( nMacroExecutionMode == MacroExecMode::USE_CONFIG_REJECT_CONFIRMATION )
135
0
            ||  ( nMacroExecutionMode == MacroExecMode::USE_CONFIG_APPROVE_CONFIRMATION )
136
0
            )
137
0
        {
138
            // check confirm first, as nMacroExecutionMode is always overwritten by the GetMacroSecurityLevel() switch
139
0
            if (nMacroExecutionMode == MacroExecMode::USE_CONFIG_REJECT_CONFIRMATION)
140
0
                eAutoConfirm = eAutoConfirmReject;
141
0
            else if (nMacroExecutionMode == MacroExecMode::USE_CONFIG_APPROVE_CONFIRMATION)
142
0
                eAutoConfirm = eAutoConfirmApprove;
143
144
0
            switch ( SvtSecurityOptions::GetMacroSecurityLevel() )
145
0
            {
146
0
                case 3: // "Very high"
147
0
                    nMacroExecutionMode = MacroExecMode::FROM_LIST_NO_WARN;
148
0
                    break;
149
0
                case 2: // "High"
150
0
                    nMacroExecutionMode = MacroExecMode::FROM_LIST_AND_SIGNED_WARN;
151
0
                    break;
152
0
                case 1: // "Medium"
153
0
                    nMacroExecutionMode = MacroExecMode::ALWAYS_EXECUTE;
154
0
                    break;
155
0
                case 0: // "Low"
156
0
                    nMacroExecutionMode = MacroExecMode::ALWAYS_EXECUTE_NO_WARN;
157
0
                    break;
158
0
                default:
159
0
                    OSL_FAIL( "DocumentMacroMode::adjustMacroMode: unexpected macro security level!" );
160
0
                    nMacroExecutionMode = MacroExecMode::NEVER_EXECUTE;
161
0
            }
162
0
        }
163
164
0
        if ( nMacroExecutionMode == MacroExecMode::NEVER_EXECUTE )
165
0
            return disallowMacroExecution();
166
167
0
        if ( nMacroExecutionMode == MacroExecMode::ALWAYS_EXECUTE_NO_WARN )
168
0
            return allowMacroExecution();
169
170
0
        SignatureState nSignatureState = SignatureState::UNKNOWN;
171
0
        const OUString sURL(m_xData->m_rDocumentAccess.getDocumentLocation());
172
0
        try
173
0
        {
174
            // get document location from medium name and check whether it is a trusted one
175
            // the service is created without document version, since it is not of interest here
176
0
            Reference< XDocumentDigitalSignatures > xSignatures(DocumentDigitalSignatures::createDefault(::comphelper::getProcessComponentContext()));
177
0
            INetURLObject aURLReferer(sURL);
178
179
0
            OUString aLocation = aURLReferer.GetMainURL( INetURLObject::DecodeMechanism::NONE );
180
181
0
            if ( !aLocation.isEmpty() && xSignatures->isLocationTrusted( aLocation ) )
182
0
            {
183
0
                return allowMacroExecution();
184
0
            }
185
186
            // at this point it is clear that the document is not in the secure location
187
0
            if ( nMacroExecutionMode == MacroExecMode::FROM_LIST_NO_WARN )
188
0
            {
189
0
                return disallowMacroExecution();
190
0
            }
191
192
            // check whether the document is signed with trusted certificate
193
0
            if ( nMacroExecutionMode != MacroExecMode::FROM_LIST )
194
0
            {
195
0
                nSignatureState = m_xData->m_rDocumentAccess.getScriptingSignatureState();
196
197
0
                if (!bHasValidContentSignature
198
0
                    && (nMacroExecutionMode == MacroExecMode::FROM_LIST_AND_SIGNED_NO_WARN
199
0
                        || nMacroExecutionMode == MacroExecMode::FROM_LIST_AND_SIGNED_WARN)
200
0
                    && m_xData->m_rDocumentAccess.macroCallsSeenWhileLoading())
201
0
                {
202
                    // When macros are required to be signed, and the document has events which call
203
                    // macros, the document content needs to be signed, too. Do it here, and avoid
204
                    // possible UI asking to always trust certificates, after which the user's choice
205
                    // to allow macros would be ignored anyway.
206
0
                    m_xData->m_bHasUnsignedContentError
207
0
                        = nSignatureState == SignatureState::OK
208
0
                          || nSignatureState == SignatureState::NOTVALIDATED;
209
0
                    return disallowMacroExecution();
210
0
                }
211
212
                // At this point, the possible values of nMacroExecutionMode are: ALWAYS_EXECUTE,
213
                // FROM_LIST_AND_SIGNED_WARN (the default), FROM_LIST_AND_SIGNED_NO_WARN.
214
                // ALWAYS_EXECUTE corresponds to the Medium security level; it should ask for
215
                // confirmation when macros are unsigned or untrusted. FROM_LIST_AND_SIGNED_NO_WARN
216
                // should not ask any confirmations. FROM_LIST_AND_SIGNED_WARN should only allow
217
                // trusted signed macros at this point; so it may only ask for confirmation to add
218
                // certificates to trusted, and shouldn't show UI when trusted list is read-only
219
                // or the macro signature can't be validated.
220
0
                const bool bAllowUI
221
0
                    = nMacroExecutionMode != MacroExecMode::FROM_LIST_AND_SIGNED_NO_WARN
222
0
                      && eAutoConfirm == eNoAutoConfirm
223
0
                      && (nMacroExecutionMode == MacroExecMode::ALWAYS_EXECUTE
224
0
                          || !SvtSecurityOptions::IsReadOnly(
225
0
                              SvtSecurityOptions::EOption::MacroTrustedAuthors))
226
0
                      && (nMacroExecutionMode != MacroExecMode::FROM_LIST_AND_SIGNED_WARN
227
0
                          || nSignatureState == SignatureState::OK);
228
229
0
                if (nMacroExecutionMode == MacroExecMode::FROM_LIST_AND_SIGNED_WARN
230
0
                    && nSignatureState != SignatureState::NOSIGNATURES
231
0
                    && nSignatureState != SignatureState::OK)
232
0
                {
233
                    // set the flag so that we can show the appropriate error & buttons
234
                    // for invalid signatures in the infobar for high macro security.
235
0
                    m_xData->m_bHasInvalidSignaturesError = true;
236
0
                }
237
238
0
                const bool bHasTrustedMacroSignature = m_xData->m_rDocumentAccess.hasTrustedScriptingSignature(bAllowUI ? rxInteraction : nullptr);
239
240
0
                if (bHasTrustedMacroSignature)
241
0
                {
242
                    // there is trusted macro signature, allow macro execution
243
0
                    return allowMacroExecution();
244
0
                }
245
0
                else if ( nSignatureState == SignatureState::OK
246
0
                       || nSignatureState == SignatureState::NOTVALIDATED )
247
0
                {
248
                    // there is valid signature, but it is not from the trusted author
249
0
                    if (eAutoConfirm == eAutoConfirmApprove
250
0
                        && nMacroExecutionMode == MacroExecMode::ALWAYS_EXECUTE)
251
0
                    {
252
                        // For ALWAYS_EXECUTE + eAutoConfirmApprove (USE_CONFIG_APPROVE_CONFIRMATION
253
                        // in Medium security mode), do not approve it right here; let Security Zone
254
                        // check below do its job first.
255
0
                    }
256
0
                    else
257
0
                    {
258
                        // All other cases of valid but untrusted signatures should result in denied
259
                        // macros here. This includes explicit reject from user in the UI in cases
260
                        // of FROM_LIST_AND_SIGNED_WARN and ALWAYS_EXECUTE
261
0
                        return disallowMacroExecution();
262
0
                    }
263
0
                }
264
                // Other values of nSignatureState would result in either rejected macros
265
                // (FROM_LIST_AND_SIGNED_*), or a confirmation.
266
0
            }
267
0
        }
268
0
        catch ( const Exception& )
269
0
        {
270
0
            DBG_UNHANDLED_EXCEPTION("sfx.doc");
271
0
        }
272
273
        // at this point it is clear that the document is neither in secure location nor signed with trusted certificate
274
0
        if ((nMacroExecutionMode == MacroExecMode::FROM_LIST_AND_SIGNED_NO_WARN)
275
0
            || (nMacroExecutionMode == MacroExecMode::FROM_LIST_AND_SIGNED_WARN))
276
0
        {
277
0
            return disallowMacroExecution();
278
0
        }
279
280
#if defined(_WIN32)
281
        // Windows specific: try to decide macros loading depending on Windows Security Zones
282
        // (is the file local, or it was downloaded from internet, etc?)
283
        OUString sFilePath;
284
        osl::FileBase::getSystemPathFromFileURL(sURL, sFilePath);
285
        sal::systools::COMReference<IZoneIdentifier> pZoneId;
286
        pZoneId.CoCreateInstance(CLSID_PersistentZoneIdentifier);
287
        sal::systools::COMReference<IPersistFile> pPersist(pZoneId, sal::systools::COM_QUERY);
288
        DWORD dwZone;
289
        if (!pPersist || !SUCCEEDED(pPersist->Load(o3tl::toW(sFilePath.getStr()), STGM_READ)) ||
290
            !SUCCEEDED(pZoneId->GetId(&dwZone)))
291
        {
292
            // no Security Zone info found -> assume a local file, not
293
            // from the internet
294
            dwZone = URLZONE_LOCAL_MACHINE;
295
        }
296
297
        // determine action from zone and settings
298
        sal_Int32 nAction;
299
        switch (dwZone) {
300
            case URLZONE_LOCAL_MACHINE:
301
                nAction = officecfg::Office::Common::Security::Scripting::WindowsSecurityZone::ZoneLocal::get();
302
                break;
303
            case URLZONE_INTRANET:
304
                nAction = officecfg::Office::Common::Security::Scripting::WindowsSecurityZone::ZoneIntranet::get();
305
                break;
306
            case URLZONE_TRUSTED:
307
                nAction = officecfg::Office::Common::Security::Scripting::WindowsSecurityZone::ZoneTrusted::get();
308
                break;
309
            case URLZONE_INTERNET:
310
                nAction = officecfg::Office::Common::Security::Scripting::WindowsSecurityZone::ZoneInternet::get();
311
                break;
312
            case URLZONE_UNTRUSTED:
313
                nAction = officecfg::Office::Common::Security::Scripting::WindowsSecurityZone::ZoneUntrusted::get();
314
                break;
315
            default:
316
                // unknown zone, let's ask the user
317
                nAction = 0;
318
                break;
319
        }
320
321
        // act on result
322
        switch (nAction)
323
        {
324
            case 0: // Ask
325
                break;
326
            case 1: // Allow
327
                if (nSignatureState != SignatureState::BROKEN
328
                    && nSignatureState != SignatureState::INVALID)
329
                    return allowMacroExecution();
330
                break;
331
            case 2: // Deny
332
                return disallowMacroExecution();
333
        }
334
#endif
335
        // confirmation is required
336
0
        bool bSecure = false;
337
338
0
        if ( eAutoConfirm == eNoAutoConfirm )
339
0
        {
340
0
            OUString sReferrer(sURL);
341
0
            osl::FileBase::getSystemPathFromFileURL(sReferrer, sReferrer);
342
343
0
            bSecure = lcl_showMacroWarning( rxInteraction, sReferrer );
344
0
        }
345
0
        else
346
0
            bSecure = ( eAutoConfirm == eAutoConfirmApprove );
347
348
0
        return ( bSecure ? allowMacroExecution() : disallowMacroExecution() );
349
0
    }
350
351
352
    bool DocumentMacroMode::isMacroExecutionDisallowed() const
353
0
    {
354
0
        return m_xData->m_rDocumentAccess.getCurrentMacroExecMode() == MacroExecMode::NEVER_EXECUTE;
355
0
    }
356
357
358
    bool DocumentMacroMode::containerHasBasicMacros( const Reference< XLibraryContainer >& xContainer )
359
0
    {
360
0
        bool bHasMacroLib = false;
361
0
        try
362
0
        {
363
0
            if ( xContainer.is() )
364
0
            {
365
                // a library container exists; check if it's empty
366
367
                // if there are libraries except the "Standard" library
368
                // we assume that they are not empty (because they have been created by the user)
369
0
                if ( !xContainer->hasElements() )
370
0
                    bHasMacroLib = false;
371
0
                else
372
0
                {
373
0
                    static constexpr OUStringLiteral aStdLibName( u"Standard" );
374
0
                    static constexpr OUStringLiteral aVBAProject( u"VBAProject" );
375
0
                    const Sequence< OUString > aElements = xContainer->getElementNames();
376
0
                    for( const OUString& aElement : aElements )
377
0
                    {
378
0
                        if( aElement == aStdLibName || aElement == aVBAProject )
379
0
                        {
380
0
                            Reference < XNameAccess > xLib;
381
0
                            Any aAny = xContainer->getByName( aElement );
382
0
                            aAny >>= xLib;
383
0
                            if ( xLib.is() && xLib->hasElements() )
384
0
                                return true;
385
0
                        }
386
0
                        else
387
0
                            return true;
388
0
                    }
389
0
                }
390
0
            }
391
0
        }
392
0
        catch( const Exception& )
393
0
        {
394
0
            DBG_UNHANDLED_EXCEPTION("sfx.doc");
395
0
        }
396
0
        return bHasMacroLib;
397
0
    }
398
399
400
    bool DocumentMacroMode::hasMacroLibrary() const
401
8.47k
    {
402
8.47k
        bool bHasMacroLib = false;
403
#if HAVE_FEATURE_SCRIPTING
404
        try
405
        {
406
            Reference< XEmbeddedScripts > xScripts( m_xData->m_rDocumentAccess.getEmbeddedDocumentScripts() );
407
            Reference< XLibraryContainer > xContainer;
408
            if ( xScripts.is() )
409
                xContainer.set( xScripts->getBasicLibraries(), UNO_QUERY_THROW );
410
            bHasMacroLib = containerHasBasicMacros( xContainer );
411
412
        }
413
        catch( const Exception& )
414
        {
415
            DBG_UNHANDLED_EXCEPTION("sfx.doc");
416
        }
417
#endif
418
8.47k
        return bHasMacroLib;
419
8.47k
    }
420
421
    bool DocumentMacroMode::hasUnsignedContentError() const
422
0
    {
423
0
        return m_xData->m_bHasUnsignedContentError;
424
0
    }
425
426
    bool DocumentMacroMode::hasInvalidSignaturesError() const
427
0
    {
428
0
        return m_xData->m_bHasInvalidSignaturesError;
429
0
    }
430
431
    bool DocumentMacroMode::storageHasMacros( const Reference< XStorage >& rxStorage )
432
8.47k
    {
433
8.47k
        bool bHasMacros = false;
434
8.47k
        if ( rxStorage.is() )
435
8.47k
        {
436
8.47k
            try
437
8.47k
            {
438
8.47k
                static constexpr OUString s_sBasicStorageName( u"Basic"_ustr );
439
8.47k
                static constexpr OUString s_sScriptsStorageName( u"Scripts"_ustr );
440
441
8.47k
                bHasMacros =(   (   rxStorage->hasByName( s_sBasicStorageName )
442
8.47k
                                &&  rxStorage->isStorageElement( s_sBasicStorageName )
443
8.47k
                                )
444
8.47k
                            ||  (   rxStorage->hasByName( s_sScriptsStorageName )
445
8.47k
                                &&  rxStorage->isStorageElement( s_sScriptsStorageName )
446
8.47k
                                )
447
8.47k
                            );
448
8.47k
            }
449
8.47k
            catch ( const Exception& )
450
8.47k
            {
451
0
                DBG_UNHANDLED_EXCEPTION("sfx.doc");
452
0
            }
453
8.47k
        }
454
8.47k
        return bHasMacros;
455
8.47k
    }
456
457
    bool DocumentMacroMode::hasMacros() const
458
8.47k
    {
459
8.47k
        return m_xData->m_rDocumentAccess.documentStorageHasMacros() || hasMacroLibrary() || m_xData->m_rDocumentAccess.macroCallsSeenWhileLoading();
460
8.47k
    }
461
462
    bool DocumentMacroMode::checkMacrosOnLoading( const Reference< XInteractionHandler >& rxInteraction, bool bHasValidContentSignature, bool bHasMacros )
463
8.47k
    {
464
8.47k
        bool bAllow = false;
465
8.47k
        if ( SvtSecurityOptions::IsMacroDisabled() )
466
8.47k
        {
467
            // no macro should be executed at all
468
8.47k
            bAllow = disallowMacroExecution();
469
8.47k
        }
470
0
        else
471
0
        {
472
0
            if (bHasMacros)
473
0
            {
474
0
                bAllow = adjustMacroMode( rxInteraction, bHasValidContentSignature );
475
0
            }
476
0
            else if ( !isMacroExecutionDisallowed() )
477
0
            {
478
                // if macros will be added by the user later, the security check is obsolete
479
0
                bAllow = allowMacroExecution();
480
0
            }
481
0
        }
482
8.47k
        return bAllow;
483
8.47k
    }
484
485
486
} // namespace sfx2
487
488
489
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */