Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/js/compiler/pool.go
2070 views
1
package compiler
2
3
import (
4
"bytes"
5
"context"
6
"fmt"
7
"reflect"
8
"sync"
9
10
"github.com/Mzack9999/goja"
11
"github.com/Mzack9999/goja_nodejs/console"
12
"github.com/Mzack9999/goja_nodejs/require"
13
"github.com/kitabisa/go-ci"
14
"github.com/projectdiscovery/gologger"
15
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libbytes"
16
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libfs"
17
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libikev2"
18
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libkerberos"
19
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libldap"
20
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libmssql"
21
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libmysql"
22
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libnet"
23
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/liboracle"
24
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libpop3"
25
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libpostgres"
26
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/librdp"
27
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libredis"
28
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/librsync"
29
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libsmb"
30
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libsmtp"
31
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libssh"
32
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libstructs"
33
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libtelnet"
34
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libvnc"
35
"github.com/projectdiscovery/nuclei/v3/pkg/js/global"
36
"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs"
37
"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/goconsole"
38
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
39
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
40
stringsutil "github.com/projectdiscovery/utils/strings"
41
syncutil "github.com/projectdiscovery/utils/sync"
42
)
43
44
const (
45
exportToken = "Export"
46
exportAsToken = "ExportAs"
47
)
48
49
var (
50
r *require.Registry
51
lazyRegistryInit = sync.OnceFunc(func() {
52
r = new(require.Registry) // this can be shared by multiple runtimes
53
// autoregister console node module with default printer it uses gologger backend
54
require.RegisterNativeModule(console.ModuleName, console.RequireWithPrinter(goconsole.NewGoConsolePrinter()))
55
})
56
pooljsc *syncutil.AdaptiveWaitGroup
57
lazySgInit = sync.OnceFunc(func() {
58
pooljsc, _ = syncutil.New(syncutil.WithSize(PoolingJsVmConcurrency))
59
})
60
sgResizeCheck = func(ctx context.Context) {
61
// resize check point
62
if pooljsc.Size != PoolingJsVmConcurrency {
63
if err := pooljsc.Resize(ctx, PoolingJsVmConcurrency); err != nil {
64
gologger.Warning().Msgf("Could not resize workpool: %s\n", err)
65
}
66
}
67
}
68
)
69
70
var gojapool = &sync.Pool{
71
New: func() interface{} {
72
return createNewRuntime()
73
},
74
}
75
76
func executeWithRuntime(runtime *goja.Runtime, p *goja.Program, args *ExecuteArgs, opts *ExecuteOptions) (result goja.Value, err error) {
77
defer func() {
78
// reset before putting back to pool
79
_ = runtime.GlobalObject().Delete("template") // template ctx
80
// remove all args
81
for k := range args.Args {
82
_ = runtime.GlobalObject().Delete(k)
83
}
84
if opts != nil && opts.Cleanup != nil {
85
opts.Cleanup(runtime)
86
}
87
runtime.RemoveContextValue("executionId")
88
}()
89
90
// TODO(dwisiswant0): remove this once we get the RCA.
91
defer func() {
92
if ci.IsCI() {
93
return
94
}
95
96
if r := recover(); r != nil {
97
err = fmt.Errorf("panic: %s", r)
98
}
99
}()
100
101
// set template ctx
102
_ = runtime.Set("template", args.TemplateCtx)
103
// set args
104
for k, v := range args.Args {
105
_ = runtime.Set(k, v)
106
}
107
// register extra callbacks if any
108
if opts != nil && opts.Callback != nil {
109
if err := opts.Callback(runtime); err != nil {
110
return nil, err
111
}
112
}
113
114
// inject execution id and context
115
runtime.SetContextValue("executionId", opts.ExecutionId)
116
117
// execute the script
118
return runtime.RunProgram(p)
119
}
120
121
// ExecuteProgram executes a compiled program with the default options.
122
// it deligates if a particular program should run in a pooled or non-pooled runtime
123
func ExecuteProgram(p *goja.Program, args *ExecuteArgs, opts *ExecuteOptions) (result goja.Value, err error) {
124
if opts.Source == nil {
125
// not-recommended anymore
126
return executeWithoutPooling(p, args, opts)
127
}
128
if !stringsutil.ContainsAny(*opts.Source, exportAsToken, exportToken) {
129
// not-recommended anymore
130
return executeWithoutPooling(p, args, opts)
131
}
132
return executeWithPoolingProgram(p, args, opts)
133
}
134
135
// executes the actual js program
136
func executeWithPoolingProgram(p *goja.Program, args *ExecuteArgs, opts *ExecuteOptions) (result goja.Value, err error) {
137
// its unknown (most likely cannot be done) to limit max js runtimes at a moment without making it static
138
// unlike sync.Pool which reacts to GC and its purposes is to reuse objects rather than creating new ones
139
lazySgInit()
140
sgResizeCheck(opts.Context)
141
142
pooljsc.Add()
143
defer pooljsc.Done()
144
runtime := gojapool.Get().(*goja.Runtime)
145
defer gojapool.Put(runtime)
146
var buff bytes.Buffer
147
opts.exports = make(map[string]interface{})
148
149
defer func() {
150
// remove below functions from runtime
151
_ = runtime.GlobalObject().Delete(exportAsToken)
152
_ = runtime.GlobalObject().Delete(exportToken)
153
}()
154
// register export functions
155
_ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{
156
Name: "Export", // we use string instead of const for documentation generation
157
Signatures: []string{"Export(value any)"},
158
Description: "Converts a given value to a string and is appended to output of script",
159
FuncDecl: func(call goja.FunctionCall, runtime *goja.Runtime) goja.Value {
160
if len(call.Arguments) == 0 {
161
return goja.Null()
162
}
163
for _, arg := range call.Arguments {
164
if out := stringify(arg, runtime); out != "" {
165
buff.WriteString(out)
166
}
167
}
168
return goja.Null()
169
},
170
})
171
// register exportAs function
172
_ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{
173
Name: "ExportAs", // Export
174
Signatures: []string{"ExportAs(key string,value any)"},
175
Description: "Exports given value with specified key and makes it available in DSL and response",
176
FuncDecl: func(call goja.FunctionCall, runtime *goja.Runtime) goja.Value {
177
if len(call.Arguments) != 2 {
178
// this is how goja expects errors to be returned
179
// and internally it is done same way for all errors
180
panic(runtime.ToValue("ExportAs expects 2 arguments"))
181
}
182
key := call.Argument(0).String()
183
value := call.Argument(1)
184
opts.exports[key] = stringify(value, runtime)
185
return goja.Null()
186
},
187
})
188
val, err := executeWithRuntime(runtime, p, args, opts)
189
if err != nil {
190
return nil, err
191
}
192
if val.Export() != nil {
193
// append last value to output
194
buff.WriteString(stringify(val, runtime))
195
}
196
// and return it as result
197
return runtime.ToValue(buff.String()), nil
198
}
199
200
// Internal purposes i.e generating bindings
201
func InternalGetGeneratorRuntime() *goja.Runtime {
202
runtime := gojapool.Get().(*goja.Runtime)
203
return runtime
204
}
205
206
func getRegistry() *require.Registry {
207
lazyRegistryInit()
208
return r
209
}
210
211
func createNewRuntime() *goja.Runtime {
212
runtime := protocolstate.NewJSRuntime()
213
_ = getRegistry().Enable(runtime)
214
// by default import below modules every time
215
_ = runtime.Set("console", require.Require(runtime, console.ModuleName))
216
217
// Register embedded javacript helpers
218
if err := global.RegisterNativeScripts(runtime); err != nil {
219
gologger.Error().Msgf("Could not register scripts: %s\n", err)
220
}
221
return runtime
222
}
223
224
// stringify converts a given value to string
225
// if its a struct it will be marshalled to json
226
func stringify(gojaValue goja.Value, runtime *goja.Runtime) string {
227
value := gojaValue.Export()
228
if value == nil {
229
return ""
230
}
231
kind := reflect.TypeOf(value).Kind()
232
if kind == reflect.Struct || kind == reflect.Ptr && reflect.ValueOf(value).Elem().Kind() == reflect.Struct {
233
// in this case we must use JSON.stringify to convert to string
234
// because json.Marshal() utilizes json tags when marshalling
235
// but goja has custom implementation of json.Marshal() which does not
236
// since we have been using `to_json` in all our examples we must stick to it
237
// marshal structs or struct pointers to json automatically
238
jsonStringify, ok := goja.AssertFunction(runtime.Get("to_json"))
239
if ok {
240
result, err := jsonStringify(goja.Undefined(), gojaValue)
241
if err == nil {
242
return result.String()
243
}
244
}
245
// unlikely but if to_json throwed some error use native json.Marshal
246
val := value
247
if kind == reflect.Ptr {
248
val = reflect.ValueOf(value).Elem().Interface()
249
}
250
bin, err := json.Marshal(val)
251
if err == nil {
252
return string(bin)
253
}
254
}
255
// for everything else stringify
256
return fmt.Sprintf("%+v", value)
257
}
258
259